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}
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)
|
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
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
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 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
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
At this time, I make sure to respect the docx format, and select the ‘Microsoft Word’ option.
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.
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
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
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
.