Contents

FCSC Prequals 2020 - Académie de l'investigation

The annual CTF organized by the ANSSI. Here is my writeup for a set of DFIR challenges around a single Linux memory dump.

1 - C’est la rentrée

Statement 1

Welcome to forensic academia! Your mission, validate a maximum number of steps of this challenges set, in order to demonstrate you GNU/Linux forensic skills.

First step: recover the machine’s HOSTNAME, the authenticated user’s name at the dump time and the machine’s Linux version.

Flag format: FCSC{hostname:user:x.x.x-x-amdxx}

Resources
  • dmp.tar.gz

Right after downloading the memory dump, I always execute these command:

$ mkdir vol

$ strings dmp.mem | tee vol/strings

And while doing the challenges, I put volatility modules outputs in the vol directory. Indeed, this is a very slow tool, especially when you specify a plugin directory, so saving its outputs is necessary to not loose too much time.

Linux version

The Linux version is the first thing to get, as it’s a part of the flag and it’s necessary to create our own volatility profile.

$ grep 'Linux version' vol/strings

1
Linux version 5.4.0-4-amd64 (debian-kernel@lists.debian.org) (gcc version 9.2.1 20200203 (Debian 9.2.1-28)) #1 SMP Debian 5.4.19-1 (2020-02-13)
Flag 1 part 3
5.4.à-4-amd64

Volatility profile

During the CTF, many people (including me) struggled to create a working profile. Even if many articles on Internet exist on how to build a Linux profile, I think more information there is on this subject, better it is. So below is how I got a working volatility profile for this memory dump.

First, we have to create a virtual machine looking like the one the chall was created on. That means having the same:

  • Linux ditribution
  • Kernel version

$ grep 'Debian GNU/Linux' vol/strings

1
2
3
...
bitness='64' distroName='Debian' distroVersion='testing' familyName='Linux' kernelVersion='5.4.0-4-amd64' prettyName='Debian GNU/Linux bullseye/sid'
...

So the challenge machine is a Debian 64-bits bullseye/sid with a 5.4.0-4-am64 kernel.

The corresponding .iso installation media can be downloaded here: https://www.debian.org/devel/debian-installer/ (choose the netinstall to save time).

At the end of the VM installation, the wizard asks us to choose what packages we want to install. A protip I know from Maki is to install only the SSH server. It will speed up the installation process, you will be able to get a shell on your VM right after installation finishes, and you just don’t need anything else.

The installed VM has 2 kernels already installed:

  • 5.4.0-4-amd64
  • 5.5.0-2-amd64

To have the same kernel than in the memory dump, we choose the first one in GRUB at boot time. Then, we enable SSH root login and we’re ready to start building the profile.

The VM already had the package linux-image-5.4.0-4-amd64, as we booted with it. But the package linux-headers-5.4.0-4-amd64 was missing in the current repositories. To install it anyway, we have to add a repository snapshot in /etc/apt/sources.list. Then, we update the packages list and install the required tools to make a volatility profile.

# echo 'https://snapshot.debian.org/archive/debian/20200326T101808Z/ sid main >> /etc/apt/sources.list'

# apt -o Acquire::Check-Valid-Until=false update # Update despite the presence of a snapshot (old) repo

# apt install build-essential zip dwarfdump linux-headers-5.4.0-4-amd64

$ git clone https://github.com/volatilityfoundation/volatility

$ cd volatility/tools/linux

$ make

$ zip ecsc.zip module.dwarf /boot/System.map-5.4.0-4-amd64

The file ecsc.zip is the wanted profile, now we get it back on our local machine using scp. We put it in a directory where are located all our volatility profiles, let’s call this dir $PROFILES.

And now, here is an ultimate protip, one that could save you hours (it actually would have saved me hours). Delete from your machine (and your mind) the volatility package, the one from apt. It is the version 2.6 of the tool. And guess what? Your profile will never, ever work if you keep using this version of volatility. So make sure you git clone the last version (2.6.1) from the official GitHub repo instead.

$ sudo apt remove --purge --delete-from-mind --insult volatility

$ git clone https://github.com/volatilityfoundation/volatility

$ ln -s $PWD/volatility/vol.py $HOME/.local/bin

Having your profile ecsc.zip in your $PROFILES dir, the following should work:

1
2
3
4
5
$ vol.py --plugins=$PROFILES --info |grep ecsc
Linuxecscx64                               - A Profile for Linux ecsc x64
$ vol.py --plugins=$PROFILES --profile=Linuxecscx64 -f dmp.mem linux_banner
Linux version 5.4.0-4-amd64 (debian-kernel@lists.debian.org) (gcc version 9.2.1 20200203 (Debian 9.2.1-28)) #1 SMP Debian 5.4.19-1 (2020-02-13)
$ vol.py --plugins=$PROFILES --profile=Linuxecscx64 -f dmp.mem -h | tee vol/help  # list of available commands

And that’s it, now we can go greps but having a volatilify profile in case :p

Username

It is quite simple to determine the user who did the capture thanks to the Bash commands history:

$ vol.py --plugins=$PROFILES --profile=Linuxecscx64 -f dmp.mem linux_bash | tee vol/bash_history

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
Pid      Name                 Command Time                   Command
-------- -------------------- ------------------------------ -------
    1523 bash                 2020-03-26 23:24:18 UTC+0000   rm .bash_history
    1523 bash                 2020-03-26 23:24:18 UTC+0000   exit
    1523 bash                 2020-03-26 23:24:18 UTC+0000   vim /home/Lesage/.bash_history
    1523 bash                 2020-03-26 23:24:27 UTC+0000   ss -laupt
    1523 bash                 2020-03-26 23:26:06 UTC+0000   rkhunter -c
    1523 bash                 2020-03-26 23:29:19 UTC+0000   nmap -sS -sV 10.42.42.0/24
    1523 bash                 2020-03-26 23:31:31 UTC+0000   ?+??U
    1523 bash                 2020-03-26 23:31:31 UTC+0000   ip -c addr
    1523 bash                 2020-03-26 23:38:00 UTC+0000   swapoff -a
    1523 bash                 2020-03-26 23:38:05 UTC+0000   swapon -a
    1523 bash                 2020-03-26 23:40:18 UTC+0000   ls
    1523 bash                 2020-03-26 23:40:23 UTC+0000   cat LiME.txt
    1523 bash                 2020-03-26 23:40:33 UTC+0000   cd LiME/src/
    1523 bash                 2020-03-26 23:40:54 UTC+0000   
    1523 bash                 2020-03-26 23:40:54 UTC+0000   insmod lime-5.4.0-4-amd64.ko "path=/dmp.mem format=lime timeout=0"

The user edited Lesage’s .bash_history

Flag 1 part 2
Lesage

Hostname

I tried to dump a file containing the hostname:

1
2
3
4
5
6
7
$ mkdir files
$ vol.py --plugins=$PROFILES --profile=Linuxecscx64 -f dmp.mem linux_enumerate_files > vol/cachedfiles
$ grep hostname vol/cachedfiles
           16262 0xffff9d72bce9dda8 /proc/sys/kernel/hostname
$ vol.py --plugins=$PROFILES --profile=Linuxecscx64 -f dmp.mem linux_find_file -i 0xffff9d72bce9dda8 -O files/hostname
$ cat files/hostname
$

But it is empty… as it is very often :(

Fortunately the combo strings+grep is here to save us once again. We can search for a user prompt, for example:

$ grep 'Lesage@' vol/strings

1
2
3
...
Lesage@challenge: ~
...

Here I did the mistake to think challenge was the full hostname. By submitting the flag FCSC{challenge:Lesage:5.4.0-4-amd64}, I was rejected.

An other grep can remove our doubts:

$ grep 'challenge' vol/strings

1
2
3
...
systemd[1]: Set hostname to <challenge.fcsc>.
...

Note: We could have search for hostname from the beginning, but it didn’t come to my mind :/

Flag 1 part 1
challenge.fcsc
Flag 1
FCSC{challenge.fcsc:Lesage:5.4.0-4-amd64}

2 - Porte dérobée

Statement 2

A remote host is connected to this one via a backdoor which allows to execute commands.

  • What is the port number listening for this connection?
  • What is the remote host’s IP address at the dump time?
  • What is the UTC timestamp of the backdoor’s process creation?

Flag format: FCSC{port:IP:YYY-MM-DD HH:MM:SS}

First things first: what’s netstat saying?

$ vol.py --plugins=$PROFILES --profile=Linuxecscx64 -f dmp.mem linux_netstat | tee vol/netsat

The output is very large because UNIX sockets are displayed too, but we cannot omit the ncat sockets.

$ grep ncat vol/netstat

1
2
3
4
5
...
TCP      fd:6663:7363:1000:c10b:6374:25f:dc37:36280 fd:6663:7363:1000:55cf:b9c6:f41d:cc24:58014 ESTABLISHED                  ncat/1515
TCP      ::              :36280 ::              :    0 LISTEN                       ncat/119711
TCP      0.0.0.0         :36280 0.0.0.0         :    0 LISTEN                       ncat/119711
...

Thanks to pstree module we can check if the the 1515 process has children:

$ vol.py --plugins=$PROFILES --profile=Linuxecscx64 -f dmp.mem linux_pstree > vol/pstree

$ grep -A 1 1515 vol/pstree

1
2
...ncat              1515            1001           
....sh               119511          1001

A shell process has been created, so we can be 99% sure that it’s the backdoor we are searching for.

We found 2 parts of the flag : the remote host fd:6663:7363:1000:55cf:b9c6:f41d:cc24 is connected through our port 36280.

Flag 2 part 1
36280
Flag 2 part 2
fd:6663:7363:1000:55cf:b9c6:f41d:cc24

We also got the PID (1515) which allows us to retrieve the process creation timestamp with pslist.

$ vol.py --plugins=$PROFILES --profile=Linuxecscx64 -f dmp.mem linux_pslist > vol/pslist

$ grep 1515 vol/pslist

1
2
0xffff9d72c014be00 ncat                 1515            1513            1001            1001   0x000000003e3d0000 2020-03-26 23:24:20 UTC+0000
0xffff9d72c5d50000 sh                   119511          1515            1001            1001   0x00000000128ac000 2020-03-26 23:32:36 UTC+0000

We found it:

Flag 2 part 3
2020-03-26 23:24:20
Flag 2
FCSC{36280:fd:6663:7363:1000:55cf:b9c6:f41d:cc24:2020-03-26 23:24:20}

3 - Rédaction

Statement 3

The document note.docx has been created just before the memory was dumped. Recover its content!

Flag format: FCSC{xxx} where xxx is a string which you will see by viewing the document’s content.

The file wasn’t cached in memory, so I couldn’t manage to solve it using volatility.

Besides, the string FCSC isn’t written in the document, so when we (filthily) grep for FCSC{, we only see a bait:

$ grep -i 'FCSC{' vol/strings

1
echo -n 'FCSC{..........' | nop

But I found a way to identify the document’s content in the memory: by creating a similar document.

I put a recognizable string in a new LibreOffice document, and save it to note.docx

/img/fcsc-prequals-2020-academie-investigation/redaction_savedocx1.png

At this time, I make sure to respect the docx format, and select the ‘Microsoft Word’ option.

/img/fcsc-prequals-2020-academie-investigation/redaction_savedocx2.png

You can dissect a docx file by unzipping it. After doing that we can identify more closely where is stored the BouzygouloumDidNothingWrong string.

$ unzip note.docx -d docx

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
Archive:  note.docx
  inflating: _rels/.rels             
  inflating: docProps/app.xml        
  inflating: docProps/core.xml       
  inflating: word/_rels/document.xml.rels  
  inflating: word/settings.xml       
  inflating: word/fontTable.xml      
  inflating: word/document.xml       
  inflating: word/styles.xml         
  inflating: [Content_Types].xml

$ cd docx && grep -rn Bouzygouloum

1
word/document.xml:2:<w:document xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:w10="urn:schemas-microsoft-com:office:word" xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape" xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing" xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" mc:Ignorable="w14 wp14"><w:body><w:p><w:pPr><w:pStyle w:val="Normal"/><w:rPr></w:rPr></w:pPr><w:r><w:rPr></w:rPr><w:t>BouzygouloumDidNothingWrong</w:t></w:r></w:p><w:sectPr><w:type w:val="nextPage"/><w:pgSz w:w="11906" w:h="16838"/><w:pgMar w:left="1134" w:right="1134" w:header="0" w:top="1134" w:footer="0" w:bottom="1134" w:gutter="0"/><w:pgNumType w:fmt="decimal"/><w:formProt w:val="false"/><w:textDirection w:val="lrTb"/></w:sectPr></w:body></w:document>

We see our string is right next to this piece of XML: <w:rPr></w:rPr></w:pPr><w:r><w:rPr></w:rPr><w:t>.

Let’s grep for it in the memory dump:

$ grep '<w:rPr></w:rPr></w:pPr><w:r><w:rPr></w:rPr><w:t>' vol/strings

1
<w:document xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:w10="urn:schemas-microsoft-com:office:word" xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape" xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing" xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" mc:Ignorable="w14 wp14"><w:body><w:p><w:pPr><w:pStyle w:val="Normal"/><w:bidi w:val="0"/><w:jc w:val="left"/><w:rPr></w:rPr></w:pPr><w:r><w:rPr></w:rPr><w:t>Nouvelle note</w:t></w:r></w:p><w:p><w:pPr><w:pStyle w:val="Normal"/><w:bidi w:val="0"/><w:jc w:val="left"/><w:rPr></w:rPr></w:pPr><w:r><w:rPr></w:rPr></w:r></w:p><w:p><w:pPr><w:pStyle w:val="Normal"/><w:bidi w:val="0"/><w:jc w:val="left"/><w:rPr></w:rPr></w:pPr><w:r><w:rPr></w:rPr><w:t>Preuve : PQHJRTSFYH-3467024-LSHRFLDFGA</w:t></w:r></w:p><w:sectPr><w:type w:val="nextPage"/><w:pgSz w:w="12240" w:h="15840"/><w:pgMar w:left="1134" w:right="1134" w:header="0" w:top="1134" w:footer="0" w:bottom="1134" w:gutter="0"/><w:pgNumType w:fmt="decimal"/><w:formProt w:val="false"/><w:textDirection w:val="lrTb"/></w:sectPr></w:body></w:documen`

Found it! Preuve : PQHJRTSFYH-3467024-LSHRFLDFGA.

Flag 3
FCSC{PQHJRTSFYH-3467024-LSHRFLDFGA}

4 - Administration

Statement 4

This machine manages a remote server using SSH protocole with a key-based authentication (password-protected key). The public key has been used to encrypt the attached message (flag.txt.enc).

Find and rebuild the key in memory which will allow to decrypt this message.

Resources
  • flag.txt.enc

The flag is encrypted with a public key, we have to decrypt it with the associated private key.

We know SSH-agent keeps plain private keys in its memory. I tried with volatility and with almost every tool developed to recover a SSH key in memory, it didn’t want to show the finger.

Only one tool gave me an interesting result: rsakeyfind. Its goal is pretty explicit. You give it a file as argument and it searches for RSA keys inside it. At the beginning I wanted to avoid using it because I thought it was very prone to false positives, but it turned out very reliable.

$ rsakeyfind dmp.mem | tee vol/rsakeyfind

 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
FOUND PRIVATE KEY AT c64ac50
version =
00
modulus =
00 d7 1e 77 82 8c 92 31 e7 69 02 a2 d5 5c 78 de
a2 0c 8f fe 28 59 31 df 40 9c 60 61 06 b9 2f 62
40 80 76 cb 67 4a b5 59 56 69 17 07 fa f9 4c bd
6c 37 7a 46 7d 70 a7 67 22 b3 4d 7a 94 c3 ba 4b
7c 4b a9 32 7c b7 38 95 45 64 a4 05 a8 9f 12 7c
4e c6 c8 2d 40 06 30 f4 60 a6 91 bb 9b ca 04 79
11 13 75 f0 ae d3 51 89 c5 74 b9 aa 3f b6 83 e4
78 6b cd f9 5c 4c 85 ea 52 3b 51 93 fc 14 6b 33
5d 30 70 fa 50 1b 1b 38 81 13 8d f7 a5 0c c0 8e
f9 63 52 18 4e a9 f9 f8 5c 5d cd 7a 0d d4 8e 7b
ee 91 7b ad 7d b4 92 d5 ab 16 3b 0a 8a ce 8e de
47 1a 17 01 86 7b ab 99 f1 4b 0c 3a 0d 82 47 c1
91 8c bb 2e 22 9e 49 63 6e 02 c1 c9 3a 9b a5 22
1b 07 95 d6 10 02 50 fd fd d1 9b be ab c2 c0 74
d7 ec 00 fb 11 71 cb 7a dc 81 79 9f 86 68 46 63
82 4d b7 f1 e6 16 6f 42 63 f4 94 a0 ca 33 cc 75
13
publicExponent =
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 01 00 01
privateExponent =
62 b5 60 31 4f 3f 66 16 c1 60 ac 47 2a ff 6b 69
00 4a b2 5c e1 50 b9 18 74 a8 e4 dc a8 ec cd 30
bb c1 c6 e3 c6 ac 20 2a 3e 5e 8b 12 e6 82 08 09
38 0b ab 7c b3 cc 9c ce 97 67 dd ef 95 40 4e 92
e2 44 e9 1d c1 14 fd a9 b1 dc 71 9c 46 21 bd 58
88 6e 22 15 56 c1 ef e0 c9 8d e5 80 3e da 7e 93
0f 52 f6 f5 c1 91 90 9e 42 49 4f 8d 9c ba 38 83
e9 33 c2 50 4f ec c2 f0 a8 b7 6e 28 25 56 6b 62
67 fe 08 f1 56 e5 6f 0e 99 f1 e5 95 7b ef eb 0a
2c 92 97 57 23 33 36 07 dd fb ae f1 b1 d8 33 b7
96 71 42 36 c5 a4 a9 19 4b 1b 52 4c 50 69 91 f0
0e fa 80 37 4b b5 d0 2f b7 44 0d d4 f8 39 8d ab
71 67 59 05 88 3d eb 48 48 33 88 4e fe f8 27 1b
d6 55 60 5e 48 b7 6d 9a a8 37 f9 7a de 1b cd 5d
1a 30 d4 e9 9e 5b 3c 15 f8 9c 1f da d1 86 48 55
ce 83 ee 8e 51 c7 de 32 12 47 7d 46 b8 35 df 41
prime1 =
00
prime2 =
00
exponent1 =
00
exponent2 =
00
coefficient =
00

We have a RSA modulus (n) and a private exponent (d). From the challenge statement, we obtained the ciphertext (c). If this is indeed the private key we are searching for, we have all we need to decrypt the flag.

Indeed, the formula to retrieve a plain message from these 3 parameters is: m = c^d % n: which corresponds to the Python code m = pow(c,d,n)

Here is my script that decrypts the file flag.txt.enc using what I got from rsakeyfind:

1
2
3
4
5
6
#!/usr/bin/env python3
n = int("00d71e77828c9231e76902a2d55c78dea20c8ffe285931df409c606106b92f62408076cb674ab55956691707faf94cbd6c377a467d70a76722b34d7a94c3ba4b7c4ba9327cb738954564a405a89f127c4ec6c82d400630f460a691bb9bca0479111375f0aed35189c574b9aa3fb683e4786bcdf95c4c85ea523b5193fc146b335d3070fa501b1b3881138df7a50cc08ef96352184ea9f9f85c5dcd7a0dd48e7bee917bad7db492d5ab163b0a8ace8ede471a1701867bab99f14b0c3a0d8247c1918cbb2e229e49636e02c1c93a9ba5221b0795d6100250fdfdd19bbeabc2c074d7ec00fb1171cb7adc81799f86684663824db7f1e6166f4263f494a0ca33cc7513", 16)
d = int("62b560314f3f6616c160ac472aff6b69004ab25ce150b91874a8e4dca8eccd30bbc1c6e3c6ac202a3e5e8b12e6820809380bab7cb3cc9cce9767ddef95404e92e244e91dc114fda9b1dc719c4621bd58886e221556c1efe0c98de5803eda7e930f52f6f5c191909e42494f8d9cba3883e933c2504fecc2f0a8b76e2825566b6267fe08f156e56f0e99f1e5957befeb0a2c92975723333607ddfbaef1b1d833b796714236c5a4a9194b1b524c506991f00efa80374bb5d02fb7440dd4f8398dab71675905883deb484833884efef8271bd655605e48b76d9aa837f97ade1bcd5d1a30d4e99e5b3c15f89c1fdad1864855ce83ee8e51c7de3212477d46b835df41", 16)
c = int.from_bytes(open("flag.txt.enc", 'rb').read(), "big")
m = int.to_bytes(pow(c,d,n), length=256, byteorder="big")
print(m)

$ ./decrypt.py

1
b"\x00\x02&\x81\xc06\xddc_F\xc2\x85>\x08\xb75\x9b\x19n]\xb0\x9f?\xa4\n\x80\x17\x04\xb3ot\x08\xc8\xed\x93\x1f)\xffC\x19\xa7\xba\x0b\xe6\xe1\x07bJCG\x1dI\xcc\x14\xbb\x0eqH\x8cv#\r\xe1\x12\xbb\x05\xf7\x82\x1b\xc8\x1fg\xdbL\x80\xfd\x03\x898\xbf\xc7\xfe\xd1\xe9c\xb3\xe9\xad\xb2{\xd1'\x94\x8b\x1d\x9b\xb3\x87K\x1b\xc3 \xe9\x06(\xa9\x91\xbc\x06m\xc4@h0\x136'\xcb\x14\x83p\xd1M\x958\xff#\x8d\x12\xf0\x02\xbc\x89#=\xaa\x0b\xed\xa1\xca,[\xc7\x14\x08~\xf8\x08bN[N\xd1\xe9\xb6+\xb6\xf6\xa3L\x88\x90s\x81\xf1`m\xe9\x89Q\xba73\xbf\x8dBn1\x04\x17\xb6\x1e9C\x00FCSC{ac5cad66114d4866a4b55e43cb8896cc4947855241b5af8d2f8a123c36083d98}\n"

If you look at the very end of the output, there is what we are searching for. I was really surprised that it worked, but we take those.

Flag 4
FCSC{ac5cad66114d4866a4b55e43cb8896cc4947855241b5af8d2f8a123c36083d98}

5 - Premiers artéfacts

Statement 5

To advance in your analysis, you have to recover:

  • The name of the process having the PID 1254.
  • The exact command that was ran on the 2020-03-26 at 23:29:19 UTC.
  • The exact number of unique IP-DST in ESTABLISHED connections at dump time.

Flag format: FCSC{processus_name:a_command:n}

Command at 2020-03-26 at 23:29:19 UTC

First, let’s search for the command time, by checking the user’s Bash history

$ cat vol/bash_history

1
2
3
...
1523 bash                 2020-03-26 23:29:19 UTC+0000   nmap -sS -sV 10.42.42.0/24
...

This one was quite quick!

Flag 5 part 2
nmap -sS -sV 10.42.42.0/24

IP-DST in TCP ESTABLISHED state

The linux_netstat’s module output should be enough:

$ cat vol/netsat | grep TCP | grep ESTABLISHED

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
TCP      10.42.42.131    :36970 116.203.52.118  :  443 ESTABLISHED                   tor/706  
TCP      10.42.42.131    :37252 163.172.182.147 :  443 ESTABLISHED                   tor/706  
TCP      fd:6663:7363:1000:c10b:6374:25f:dc37:36280 fd:6663:7363:1000:55cf:b9c6:f41d:cc24:58014 ESTABLISHED                  ncat/1515
TCP      10.42.42.131    :47106 216.58.206.226  :  443 ESTABLISHED              chromium/119187
TCP      10.42.42.131    :55224 151.101.121.140 :  443 ESTABLISHED              chromium/119187
TCP      10.42.42.131    :55226 151.101.121.140 :  443 ESTABLISHED              chromium/119187
TCP      10.42.42.131    :53190 104.124.192.89  :  443 ESTABLISHED              chromium/119187
TCP      10.42.42.131    :45652 35.190.72.21    :  443 ESTABLISHED              chromium/119187
TCP      10.42.42.131    :47102 216.58.206.226  :  443 ESTABLISHED              chromium/119187
TCP      10.42.42.131    :47104 216.58.206.226  :  443 ESTABLISHED              chromium/119187
TCP      10.42.42.131    :38186 216.58.213.142  :  443 ESTABLISHED              chromium/119187
TCP      10.42.42.131    :47100 216.58.206.226  :  443 ESTABLISHED              chromium/119187
TCP      10.42.42.131    :50612 104.93.255.199  :  443 ESTABLISHED              chromium/119187
TCP      10.42.42.131    :58772 185.199.111.154 :  443 ESTABLISHED              chromium/119187
TCP      10.42.42.131    :38184 216.58.213.142  :  443 ESTABLISHED              chromium/119187
TCP      10.42.42.131    :57000 10.42.42.134    :   22 ESTABLISHED                   ssh/119468
TCP      fd:6663:7363:1000:c10b:6374:25f:dc37:36280 fd:6663:7363:1000:55cf:b9c6:f41d:cc24:58014 ESTABLISHED                    sh/119511
TCP      127.0.0.1       :38498 127.0.0.1       :34243 ESTABLISHED                   cli/119514
TCP      127.0.0.1       :34243 127.0.0.1       :38498 ESTABLISHED                   cli/119514
TCP      10.42.42.131    :51858 10.42.42.128    :  445 ESTABLISHED             smbclient/119577

According to this output, this is our IP-DST number:

$ echo $(($(cat vol/netstat |grep 'TCP' |grep 'ESTABLISHED' |awk '{print $4}' |grep '\.' |sort -u |wc -l) + 1)) # +1 because awk didn't take account of the IPv6 address

1
13
Flag 5 part 3
nmap -sS -sV 10.42.42.0/24:13

PID 1254

I struggled with this one. While searching doing my researches in the memory dump, I was looking at this page: https://github.com/volatilityfoundation/volatility/wiki/Linux-Command-Reference, where commands for volatility Linux profiles are listed.

I have tried all these modules:

  • linux_pslist
  • linux_psaux
  • linux_pstree
  • linux_pslist_cache
  • linux_pidhashtable
  • linux_psxview
  • linux_lsof

Either they didn’t know about the 1254 process or either they didn’t work with my profile. And of course, the official documentation doesn’t mention the linux_psscan module, being the only one that worked. That triggered me a lot. Anyway, this will output the process name corresponding to the PID 1254:

$ vol.py --plugins=$PROFILES --profile=Linuxecscx64 -f dmp.mem linux_psscan > vol/psscan

$ grep 1254 vol/psscan

1
0x000000003fdccd80 pool-xfconfd         1254            -               -1              -1     0x0fd08ee88ee08ec0 -
Flag 5 part 1
pool-xfconfd
Flag 5
FCSC{pool-xfconfd:nmap -sS -sV 10.42.42.0/24:13}

6 - Dans les nuages

Statement 6

The machine being analyzed is connected to a web server at the address 10.42.42.132. The server is protected by an authentication.

Recover the username and the password of this connection.

Flag format: FCSC{username:password}

I haven’t been able to solve this challenge during the CTF, but here is a TL;DR of a possible solution (credits to Maki).

By running some linux_yarascan and linux_pstree on the memory dump, we can determine that the interesting Chromium process has the PID 119187.

We dump its memory using linux_dump_map and we search 16-bits little-endian strings:

$ mkdir pid116187

$ vol.py --plugins=$PROFILES --profile=Linuxecscx64 -f dmp.mem linux_dump_map -p 119187 -D pid116187

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
Task       VM Start           VM End                         Length Path
---------- ------------------ ------------------ ------------------ ----
    119187 0x00005649c7d25000 0x00005649ca5f2000          0x28cd000 pid116187/task.119187.0x5649c7d25000.vma
...
    119187 0x00007fb198000000 0x00007fb198021000            0x21000 pid116187/task.119187.0x7fb198000000.vma
$ cd pid116187
$ strings --print-file-name *.vma | grep 'http://10.42.42.132'
task.119187.0x7fb1cc000000.vma: http://10.42.42.132/
task.119187.0x7fb1cc000000.vma: http://10.42.42.132/
task.119187.0x7fb1cc000000.vma: http://10.42.42.132/favicon.ico
task.119187.0x7fb1cc000000.vma: http://10.42.42.132/
task.119187.0x7fb1cc000000.vma: http://10.42.42.132/panel/
$ strings -e l -n 6 task.119187.0x7fb1cc000000.vma
Admin3Kz7
5sdtYh68
 !&(/01=CEIORS_bcfghmvwxy}
-78;?ABDFGLMNPTUVW[]jkz{|
+234569HJKQXYZq~

So the credz are Admin3Kz7:5sdtYh68.

Flag 6
FCSC{Admin3Kz7:5sdtYh68}