Contents

KCSC CTF 2023

Some Forensics challenges in KCSC CTF 2023

Foreword

This CTF event has been one of the greatest events in Viet Nam so far.

My team got second place by chance in 8 hours. Time is short but we got so many works that need to be done, GGWP xD.

/images/writeups/kcsc/rank.png
Rank

Tin học văn phòng

Note

Hãy phân tích tận gốc rễ mẫu mã độc tống tiền nguy hiểm nhất năm 2023 và tìm flag.

Zip password: kcscctf

Note: Flag has 2 parts

Unzip the given file, we got one doc file. Probably, this one must contain at least a macro.

Let’s extract the macro content by using olevba tool.

Here is the content of it.

/images/writeups/kcsc/ole.png
Source code

We can see in the flag, it has $phlac. It looks like ps1 variable but there’s no such a thing appear in the macro. Hmmmmm, sussy isn’t it?

Let’s go deep down this macro.

First, we have Auto_Open() part which maybe, will execute a powershell command.

1
If Val(Application.Version) > 14

The macro check if our Office version (Word version in this case) is not from 14 or later, it will not execute.

1
2
3
cmdType = objCmdShape.Name
cmdCommand = objCmdShape.AlternativeText
cmdParams = Split(objCmdShape.TextFrame.TextRange.Text, "|")

The real malicious content is right here. The macro will take objCmdShape.AlternativeText as cmd command to execute it. But where to find it?

1
2
3
4
5
6
Sub AutoOpen()
    Auto_Open
End Sub
Sub Workbook_Open()
    Auto_Open
End Sub

Those will call the main function. Not interesting.

So, at this point, we will have two ways to continue the “investigation”.

First, we can run this macro with the newest version of Word in a virtual environment.

/images/writeups/kcsc/meme.png
yep

Since the macro called powershell command, Windows has 90% chance can catch and store it in powershell evtx log file.

Open Windows Powershell evtx, navigate to the nearest event which has ID 800.

/images/writeups/kcsc/pwsh.png
Windows Event Log

Now, you can decode the base64 string to get the real thing here.

1
2
3
4
import base64
s = 'JAB7AHAANAB5AEwAMAA0AEQAfQA9ACgAJwBNANkeJwArACcAdAAgACcAKwAnAHMAJwArACcA0R4gACcAKwAnAHQA4AAnACsAJwBpACAAJwArACcAbAAnACsAKAAiAHsAMQB9AHsAMAB9ACIAIAAtAGYAIAAnAHUAIAAnACwAJwBpAMceJwApACsAJwBxAHUAYQAnACsAJwBuACAAJwArACcAdAByAM0eJwArACcAbgBnACAAJwArACcAYwDnHmEAJwArACcAIAAnACsAJwBiACcAKwAnAKEebgAgACcAKwAnABEBJwArACcA4wAgACcAKwAnAGIAyx4nACsAJwAgACcAKwAnAG0AJwArACcA4wAgACcAKwAoACgAIgBoAPMAYQAhACEAYABuABABwx4gACIAKwAnACcAKQArACcAJwApACsAKAAiAHsAMAB9AHsAMQB9ACIAIAAtAGYAIAAnABEBsAEnACwAJwDjHmMAJwApACsAJwAgACcAKwAnAGgAsAHbHicAKwAnAG4AZwAgACcAKwAnAGQAJwArACcAqx5uACAAJwArACcAZwBpAKMeJwArACcAaQAgACcAKwAnAG0AJwArACcA4wAsACAAJwArACcAYgChHm4AJwArACcAIAAnACsAJwBjAKceJwArACcAbgAgACcAKwAoACIAewAxAH0AewAwAH0AIgAgAC0AZgAnAHUAeQAnACwAJwBjAGgAJwApACsAJwDDHicAKwAnAG4AIAAnACsAJwBrAGgAJwArACgAIgB7ADEAfQB7ADAAfQAiAC0AZgAnAKMebgAgACcALAAnAG8AJwApACsAJwAxACcAKwAoACIAewAwAH0AewAxAH0AIgAtAGYAIAAnADAAMAAnACwAJwBrACAAJwApACsAJwB2AOAAJwArACcAbwAgACcAKwAnAHMAJwArACcA0R4gACcAKwAnAHQA4ABpACcAKwAnACAAJwArACgAIgB7ADEAfQB7ADAAfQAiACAALQBmACcAox4nACwAJwBrAGgAbwAnACkAKwAnAG4AJwArACcAIAAnACsAKAAoACIAcwBhAHUAOgBgAG4AMQAwADEAMAAxADAANwAxADEAMgAwADAAMABgAG4AQwBoAOceIAAiACsAJwAnACkAKwAnACcAKQArACcAdADgACcAKwAnAGkAIAAnACsAJwBrAGgAJwArACgAIgB7ADEAfQB7ADAAfQAiACAALQBmACAAJwA6ACcALAAnAG8Aox5uACcAKQArACcAIAAnACsAJwBOACcAKwAnAGcAdQB5ACcAKwAnAMUebgAgACcAKwAnAFQAaAAnACsAKAAiAHsAMQB9AHsAMAB9ACIAIAAtAGYAJwAgACcALAAnAGEAbgBoACcAKQArACgAKAAiAEwAbwBuAGcAYABuAE4AZwDiAG4AIAAiACsAJwAnACkAKwAnACcAKQArACcAaAAnACsAKAAiAHsAMAB9AHsAMQB9ACIAIAAtAGYAIAAnAOAAbgBnACcALAAnACAAJwApACsAJwBNACcAKwAnAEIAIAAnACsAJwBCAGEAJwArACcAbgBrACcAKQANAAoAJgAoACcAQQAnACsAJwBkACcAKwAoACIAewAwAH0AewAxAH0AIgAtAGYAIAAnAGQALQBUACcALAAnAHkAcABlACcAKQApACAALQBBAHMAcwBlAG0AYgBsAHkATgBhAG0AZQAgACgAIgB7ADEAfQB7ADYAfQB7ADAAfQB7ADMAfQB7ADUAfQB7ADQAfQB7ADIAfQAiAC0AZgAgACcAdAAuAFYAJwAsACgAIgB7ADAAfQB7ADEAfQAiAC0AZgAgACcATQBpAGMAcgAnACwAJwBvAHMAJwApACwAJwBpAGMAJwAsACcAaQBzACcALAAoACIAewAxAH0AewAwAH0AIgAgAC0AZgAgACcAcwAnACwAJwBsAEIAYQAnACkALAAnAHUAYQAnACwAJwBvAGYAJwApADsADQAKAHMAZQBUAC0ASQB0AEUATQAgACgAIgBWACIAKwAiAGEAcgBJAGEAYgAiACsAIgBsAGUAOgBoAFYASQA5ADYAIgApACAAKAAgAFsAVAB5AHAARQBdACgAIgB7ADQAfQB7ADUAfQB7ADcAfQB7ADEAfQB7ADMAfQB7ADYAfQB7ADAAfQB7ADgAfQB7ADIAfQAiACAALQBGACAAJwBjAC4ASQBuAFQARQBSACcALAAnAGIAJwAsACcASQBPAE4AJwAsACcAYQAnACwAJwBtAGkAQwByAE8AcwBPAGYAJwAsACcAdAAuAFYAaQBTACcALAAnAHMASQAnACwAJwB1AEEAbAAnACwAJwBBAEMAVAAnACkAIAApACAAOwAgAFMARQB0AC0AaQB0AEUATQAgACgAJwB2AEEAJwArACcAUgBpAGEAYgAnACsAJwBsAGUAOgAnACsAJwBPADcAdgAnACsAJwA4AGsAJwApACAAIAAoACAAIABbAFQAWQBQAEUAXQAoACIAewA0AH0AewA1AH0AewAzAH0AewAyAH0AewAwAH0AewAxAH0AIgAtAEYAIAAnAC4ATQBTAEcAQgBPAHgAUwB0ACcALAAnAHkATABlACcALAAnAC4AVgBJAHMAdQBhAGwAYgBBAFMAaQBjACcALAAnAFMAbwBmAHQAJwAsACcATQBpAEMAJwAsACcAcgBPACcAKQAgACAAKQAgADsAIAAgACAAIAAoACAAZwBlAHQALQB2AEEAUgBJAEEAQgBsAEUAIAAoACcAaAB2AEkAOQAnACsAJwA2ACcAKQAgACAALQB2AGEAbABVAEUATwBOAGwAIAApADoAOgBNAHMAZwBCAG8AeAAoACQAewBwADQAeQBMADAANABEAH0ALAAgACAAJABPADcAVgA4AEsAOgA6AEkAbgBmAG8AcgBtAGEAdABpAG8AbgAsACAAKAAiAHsAMQB9AHsANAB9AHsAOAB9AHsAMQAxAH0AewA5AH0AewAwAH0AewAyAH0AewAzAH0AewA2AH0AewAxADAAfQB7ADcAfQB7ADUAfQAiACAALQBmACAAJwBjAPkAbgBnACAAdABoALABoQFuAGcAJwAsACcAQwBoAPoAJwAsACcAIAAnACwAJwB0ACcALAAnAG4AZwAgAHQA9AAnACwAJwBpAG4AJwAsACcAaQAnACwAJwAgAHQAJwAsACcAaQAgACcALAAnACAAJwAsACcAvx5jACAAYgDhAG8AJwAsACcAIAB2APQAJwApACkAOwANAAoALgAoACIAewAzAH0AewAwAH0AewAxAH0AewAyAH0AIgAgAC0AZgAoACIAewAxAH0AewAwAH0AIgAtAGYAIAAnAHQAcAAnACwAJwAtAE8AdQAnACkALAAnAHUAJwAsACcAdAAnACwAKAAiAHsAMAB9AHsAMQB9ACIALQBmACcAVwAnACwAJwByAGkAdABlACcAKQApACAAJAB7AHAANAB5AEwAMAA0AEQAfQAgAHwAIAAuACgAIgB7ADEAfQB7ADAAfQB7ADIAfQAiACAALQBmACgAIgB7ADAAfQB7ADEAfQB7ADIAfQAiAC0AZgAnAHUAJwAsACcAdAAtACcALAAnAEYAaQBsACcAKQAsACcATwAnACwAJwBlACcAKQAgAC0ARQBuAGMAbwBkAGkAbgBnACAAKAAiAHsAMQB9AHsAMAB9ACIALQBmACcAZgA4ACcALAAnAHUAdAAnACkAIAAiAEMAOgBcAFUAcwBlAHIAcwBcACQAZQBuAHYAOgBVAHMAZQByAE4AYQBtAGUAXABEAGUAcwBrAHQAbwBwAFwASABhAGMAawBlAGQALgB0AHgAdAAiAA0ACgAkAHsAVwBzAGAAQwByAGkAcABUAH0AIAA9ACAALgAoACIAewAwAH0AewAyAH0AewAxAH0AIgAtAGYAJwBOAGUAdwAtAE8AJwAsACcAYwB0ACcALAAnAGIAagBlACcAKQAgAC0AYwBvAG0AIAAoACIAewAxAH0AewAwAH0AewAyAH0AIgAtAGYAJwBjACcALAAnAHcAcwAnACwAJwByAGkAcAB0AC4AcwBoAGUAbABsACcAKQA7ACAAMQAuAC4ANQAwACAAfAAgAC4AKAAnACUAJwApACAAewAgACQAewB3AGAAUwBDAHIAaQBgAFAAdAB9AC4AIgBzAGAAZQBuAGQASwBgAEUAWQBzACIAKABbAGMAaABhAHIAXQAxADcANQApACAAfQA7AA0ACgAmACgAIgB7ADIAfQB7ADAAfQB7ADEAfQAiACAALQBmACAAJwB0ACcALAAnAGEAcgB0AC0AUAByAG8AYwBlAHMAcwAnACwAJwBTACcAKQAgACgAIgB7ADIAfQB7ADEAfQB7ADAAfQAiACAALQBmACAAJwBsAG8AcgBlACcALAAnAHAAJwAsACcAaQBlAHgAJwApACAALQBBAHIAZwB1AG0AZQBuAHQATABpAHMAdAAgACgAIgB7ADEAfQB7ADYAfQB7ADIAfQB7ADAAfQB7ADUAfQB7ADMAfQB7ADcAfQB7ADQAfQAiAC0AZgAgACcAdwB3AC4AeQBvAHUAdAB1AGIAZQAuAGMAJwAsACcALQBrACAAJwAsACcAOgAvAC8AdwAnACwAJwBtAC8AdwAnACwAJwBwAFIAcQBzADQAawAnACwAJwBvACcALAAnAGgAdAB0AHAAcwAnACwAJwBhAHQAYwBoAD8AdgA9AEIATQBFAGQAQgAnACkADQAKACQAewBwAGgAbABhAGMAfQA9ACgAIgB7ADIAfQB7ADUAfQB7ADAAfQB7ADQAfQB7ADEAfQB7ADMAfQAiACAALQBmACAAJwB5ACcALAAoACIAewAyAH0AewAxAH0AewAwAH0AIgAgAC0AZgAgACgAIgB7ADIAfQB7ADEAfQB7ADAAfQAiACAALQBmACcASAAnACwAKAAiAHsAMAB9AHsAMQB9ACIAIAAtAGYAJwBfAGEAJwAsACcAaAAxACcAKQAsACcAXwBtADMAJwApACwAJwAwAHQAJwAsACcAdQBfAGcAJwApACwAKAAiAHsAMAB9AHsAMgB9AHsAMQB9ACIALQBmACAAJwBUAHIAMAAnACwAKAAiAHsAMAB9AHsAMQB9ACIAIAAtAGYAIAAnADQAJwAsACgAIgB7ADAAfQB7ADEAfQAiACAALQBmACgAIgB7ADAAfQB7ADEAfQAiAC0AZgAnAGwAdwAnACwAJwBAAHIAJwApACwAJwAzACcAKQApACwAKAAiAHsAMQB9AHsAMAB9ACIALQBmACcAXwBtACcALAAnAGwAbAAnACkAKQAsACcAaQAnACwAJwAwACcALAAnAF8AJwApADsALgAoACIAewA0AH0AewAwAH0AewAzAH0AewAyAH0AewAxAH0AIgAtAGYAJwAtAFYAYQAnACwAJwBlACcALAAnAGEAYgBsACcALAAnAHIAaQAnACwAKAAiAHsAMAB9AHsAMQB9ACIAIAAtAGYAJwBSACcALAAoACIAewAwAH0AewAxAH0AIgAtAGYAJwBlAG0AbwAnACwAJwB2AGUAJwApACkAKQAgACgAIgB7ADEAfQB7ADAAfQAiACAALQBmACAAJwBjACcALAAoACIAewAwAH0AewAxAH0AIgAgAC0AZgAnAHAAaABsACcALAAnAGEAJwApACkA'

print(base64.b64decode(s).decode('UTF-16LE'))

Why I knew it’s UTF-16LE? Here is why:

/images/writeups/kcsc/cyberchef.png
Cyberchef

The second way, extract this file and look up some important pieces of information. We can use binwalk or a normal zip program to extract xml contents.

/images/writeups/kcsc/xml1.png
XML

We got some macro’s names here.

/images/writeups/kcsc/xml2.png
Sign

Go to the AlternateContent tag in document.xml.

/images/writeups/kcsc/xml3.png
Malicious base64 string

Concatenate two base64 strings then you’ll have the malicious content.

1
2
3
4
5
6
7
${p4yL04D}=('Mộ'+'t '+'s'+'ố '+'tà'+'i '+'l'+("{1}{0}" -f 'u ','iệ')+'qua'+'n '+'trọ'+'ng '+'của'+' '+'b'+'ạn '+'đ'+'ã '+'bị'+' '+'m'+'ã '+(("hóa!!`nĐể "+'')+'')+("{0}{1}" -f 'đư','ợc')+' '+'hướ'+'ng '+'d'+'ẫn '+'giả'+'i '+'m'+'ã, '+'bạn'+' '+'cầ'+'n '+("{1}{0}" -f'uy','ch')+'ể'+'n '+'kh'+("{1}{0}"-f'ản ','o')+'1'+("{0}{1}"-f '00','k ')+'và'+'o '+'s'+'ố '+'tài'+' '+("{1}{0}" -f'ả','kho')+'n'+' '+(("sau:`n1010107112000`nChủ "+'')+'')+'tà'+'i '+'kh'+("{1}{0}" -f ':','oản')+' '+'N'+'guy'+'ễn '+'Th'+("{1}{0}" -f' ','anh')+(("Long`nNgân "+'')+'')+'h'+("{0}{1}" -f 'àng',' ')+'M'+'B '+'Ba'+'nk')
&('A'+'d'+("{0}{1}"-f 'd-T','ype')) -AssemblyName ("{1}{6}{0}{3}{5}{4}{2}"-f 't.V',("{0}{1}"-f 'Micr','os'),'ic','is',("{1}{0}" -f 's','lBa'),'ua','of');
seT-ItEM ("V"+"arIab"+"le:hVI96") ( [TypE]("{4}{5}{7}{1}{3}{6}{0}{8}{2}" -F 'c.InTER','b','ION','a','miCrOsOf','t.ViS','sI','uAl','ACT') ) ; SEt-itEM ('vA'+'Riab'+'le:'+'O7v'+'8k')  (  [TYPE]("{4}{5}{3}{2}{0}{1}"-F '.MSGBOxSt','yLe','.VIsualbASic','Soft','MiC','rO')  ) ;    ( get-vARIABlE ('hvI9'+'6')  -valUEONl )::MsgBox(${p4yL04D},  $O7V8K::Information, ("{1}{4}{8}{11}{9}{0}{2}{3}{6}{10}{7}{5}" -f 'cùng thương','Chú',' ','t','ng tô','in','i',' t','i ',' ','ếc báo',' vô'));
.("{3}{0}{1}{2}" -f("{1}{0}"-f 'tp','-Ou'),'u','t',("{0}{1}"-f'W','rite')) ${p4yL04D} | .("{1}{0}{2}" -f("{0}{1}{2}"-f'u','t-','Fil'),'O','e') -Encoding ("{1}{0}"-f'f8','ut') "C:\Users\$env:UserName\Desktop\Hacked.txt"
${Ws`CripT} = .("{0}{2}{1}"-f'New-O','ct','bje') -com ("{1}{0}{2}"-f'c','ws','ript.shell'); 1..50 | .('%') { ${w`SCri`Pt}."s`endK`EYs"([char]175) };
&("{2}{0}{1}" -f 't','art-Process','S') ("{2}{1}{0}" -f 'lore','p','iex') -ArgumentList ("{1}{6}{2}{0}{5}{3}{7}{4}"-f 'ww.youtube.c','-k ','://w','m/w','pRqs4k','o','https','atch?v=BMEdB')
${phlac}=("{2}{5}{0}{4}{1}{3}" -f 'y',("{2}{1}{0}" -f ("{2}{1}{0}" -f'H',("{0}{1}" -f'_a','h1'),'_m3'),'0t','u_g'),("{0}{2}{1}"-f 'Tr0',("{0}{1}" -f '4',("{0}{1}" -f("{0}{1}"-f'lw','@r'),'3')),("{1}{0}"-f'_m','ll')),'i','0','_');.("{4}{0}{3}{2}{1}"-f'-Va','e','abl','ri',("{0}{1}" -f'R',("{0}{1}"-f'emo','ve'))) ("{1}{0}" -f 'c',("{0}{1}" -f'phl','a'))

When the macro executes this code, it will open a text box, a Youtube video. It also writes text box’s content to a file named Hacked.txt. Now we have $phlac which is our goal.

Last part of the flag:

/images/writeups/kcsc/flag1.png
Flag
FLAG:

KCSC{H1_1m_sUcky_Tr0ll_m4lw@r3_y0u_g0t_m3_ah1Hi}


Dropper

The challenge’s name is Dropper, I thought maybe there’s something related to a malware/code that drop a payload/real malware.

They give us an evidence.vhdx. If you want to load it to Autopsy like me, you can use Hyper-V Manager to convert it to vhd format. It’s a program on Windows.

This is a disk image of Windows C’s drive.

/images/writeups/kcsc/info.png
Drive’s information

I looked up some deleted files and got this:

/images/writeups/kcsc/delete.png
Deleted files

Why have they been deleted?

I tried looking up some useful information in the Users folder. John user gave me nothing but Public did :D

/images/writeups/kcsc/list.png
Suspicious file

I will explain here a little bit before going further. Why do the ps1 file’s name look strange?

If you mount the image and navigate to this path C\Users\Public\ChromeUpdate, you will see there’s only and just only one file.

/images/writeups/kcsc/readme.png

The size is 0 bytes, just like Autopsy has shown.

/images/writeups/kcsc/property.png
ADS sign

But why is the size on disk more than 600KB?

This size is the same as Autopsy’s result if you add the slack too.

/images/writeups/kcsc/autopsy.png
ADS showed in Autopsy

This “phenomenon” called Alternate Data Stream (ADS)

Alternate Data Streams (ADS) is a file attribute only found on the NTFS file system. It allow files to contain more than one stream of data.

So, how to open it?

/images/writeups/kcsc/dir.png
ADS showed when using cmd

You can use dir command with /r option to list it. As you can see, we have the hidden ps1 file now.

Using this

1
Get-Content E:\C\Users\Public\ChromeUpdate\readme.txt:twenty.ps1 > D:\Local-Lab\Workspace\kcsc\evidence\twenty.ps1

to get the file.

I tried to open the file but it’s not a normal file.

/images/writeups/kcsc/header.png
CAB header

At that time, after doing a search about MSCF header, I got this:

/images/writeups/kcsc/search.png

So it’s a cabinet file (Documents). We can use cabextract on linux to extract it’s content.

/images/writeups/kcsc/error.png
Error when dumping the file out

Somehow, I got an error when trying to extract it. When I tried using the file from Autopsy and got succeeded :D?

/images/writeups/kcsc/ok.png

Let’s analyze the ps1 file.

The powershell script is just about AES encryption with given key and IV.

/images/writeups/kcsc/aes.png

Here is the key and ciphertext.

/images/writeups/kcsc/main.png
Code flow

The code above splits the first 16 bytes of ciphertext as the IV. After that, it will write decrypt data as a gzip file.

I decrypted and decompressed then realized that, the result is just another AES encryption.

After doing the decrypt process about 11 times, I wrote an automatic extract-decode script (╥_╥):

 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
from Crypto.Cipher import AES
import base64
import re
import os

for i in range(1,50):
    try:
        print(f'Attemps: {i}')
        pattern = r"FromBase64String\(\".*?\"\)"
        f = open(f"test-{i}", 'r').read()
        res = re.findall(pattern, f)
        # print(len(res))
        # print(key)

        if len(res[0]) > len(res[1]): 
key = res[1].replace("FromBase64String(\"","").strip(res[1][-2:])
text = res[0].replace("FromBase64String(\"","").strip(res[0][-2:])
        else:
key = res[0].replace("FromBase64String(\"","").strip(res[0][-2:])
text = res[1].replace("FromBase64String(\"","").strip(res[1][-2:])

        print(key,end=' ')
        key = base64.b64decode(key)
        text = base64.b64decode(text)
        iv = text[:16]
        ct = text[16:]
        print(len(key))

        header = bytes.fromhex('1F8B0800000000000400')
        
        if '[System.Security.Cryptography.CipherMode]::CBC' in f:
print('CBC')
cipher = AES.new(key, AES.MODE_CBC, iv)
        elif '[System.Security.Cryptography.CipherMode]::ECB' in f:
print('ECB')
cipher = AES.new(key, AES.MODE_ECB)

        data = cipher.decrypt(ct)
        if header not in data:
data = header + data
        out = open(f'test-{i+1}.gz','wb')
        # print(data)
        out.write(data)
        out.close()
        os.system(f'gzip -dc < test-{i+1}.gz > test-{i+1}')
    except:
        print("THE END OF DECRYPTION")
        break

While I was decrypting, at some ps1 files, it’s not GzipStream but it is DeflateStream, when you tried decrypting, gzip file will be missing the signature. So I decided to add it to the script.

/images/writeups/kcsc/gzip.png
Log

Twenty decryption has been run, I see that’s why the author named it twenty.ps1 ¯\_(ツ)_/¯

/images/writeups/kcsc/mal.png
Final script block

The last file was another ps1 with base64 strings in it. Decode those, you will get pictures with flag in one of them.

/images/writeups/kcsc/flag2.jpg
Flag
FLAG:

KCSC{Som3one’s_thr0ugh1s_KMA_don’t_have_researcher?}


Action Capture

Note
I know all what you type and click

Sound like a keylogger 凸( ̄ヘ ̄)

The challenge give us a pcap file. Let’s see what’s inside.

/images/writeups/kcsc/pcap.png
Wireshark Log

There’re two protocols that we need to pay attention to: TCP and ICMP.

Followed TCP stream then I got this:

/images/writeups/kcsc/tcp.png
TCP Log

So this is a capture of a linux session. We can see linux commands like whoami, id and the most important is xinput.

/images/writeups/kcsc/xinput.png
xinput log

Looks like hacker trying to exfiltrate keyboard event with xinput and ping command.

/images/writeups/kcsc/key.png
Suspicious command

TCP’s second stream contains exfiltrating phase by dumping mouse movement in hex format then exfiltrating it with ping again.

/images/writeups/kcsc/mouse.png

Let’s take a look in ICMP packets.

/images/writeups/kcsc/ping1.png
There’re so many ICMP packets
/images/writeups/kcsc/ping2.png
Yep too much
/images/writeups/kcsc/ping3.png
Payload is not normal

Exfiltrate data has been shown clearly in ICMP packets’ data field. At this point, we need to know how xinput works to recover the data.

/images/writeups/kcsc/keytest.png
Testing command in local
/images/writeups/kcsc/mousetest.png
Output

Hmmmm interesting.

I decided to dump out ICMP’s data by using tshark. Here are my commands.

1
2
$ tshark -r ActionCapture.pcapng -Y "ip.src == 192.168.25.135 && icmp && frame.number <= 867" -Tfields -e data.data > ping_data_1
$ tshark -r ActionCapture.pcapng -Y "ip.src == 192.168.25.135 && icmp && frame.number > 867" -Tfields -e data.data > ping_data_2

The first one was the keyboard event, the second was the mouse. Because the hacker sent data to the ip 192.168.253.27, we can filter one ip out for convenience. Btw, the frame number of two phases is limited at 867, I figured it out by “hands” 😶

After that, I wrote a small script to convert those hex values back to xinput’s output like in terminal:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
f = open('ping_data_1','r').readlines()
out = open('key','w')
lst = []
s = ''
idx = 1
for i in f:
    # print(i[24:44])
    if idx % 3 == 0:
        lst.append(s)
        s = ''
    data = i[24:44]
    # print(data)
    s += data
    # print(bytes.fromhex(data.strip('0a')).decode())
    idx += 1

for i in lst:
    # print(i)
    # print(bytes.fromhex(i).decode())
    data = bytes.fromhex(i).decode()
    out.write(data)
    # out.write('\n')

Exfiltrated data here:

/images/writeups/kcsc/data1.png
Keyboard captured
/images/writeups/kcsc/data2.png
Mouse movement

For keyboard, I found a script online: https://github.com/Wh1t3Rh1n0/xinput-keylog-decoder

But I only use its keymap, here is my own script:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
keymap = {9: '<ESC>', 67: '<F1>', 68: '<F2>', 69: '<F3>', 70: '<F4>', 71: '<F5>', 72: '<F6>', 73: '<F7>', 74: '<F8>', 75: '<F9>', 76: '<F10>', 95: '<F11>', 96: '<F12>', 118: '<INS>', 119: '<DEL>', 49: '`', 10: '1', 11: '2', 12: '3', 13: '4', 14: '5', 15: '6', 16: '7', 17: '8', 18: '9', 19: '0', 20: '-', 21: '=', 22: '<BACKSPACE>', 23: '<TAB>', 24: 'q', 25: 'w', 26: 'e', 27: 'r', 28: 't', 29: 'y', 30: 'u', 31: 'i', 32: 'o', 33: 'p', 34: '[', 35: ']', 51: '\\', 66: '<CAPSLOCK>', 38: 'a', 39: 's', 40: 'd', 41: 'f', 42: 'g', 43: 'h', 44: 'j', 45: 'k', 46: 'l', 47: ';', 48: "'", 36: '<ENTER>', 52: 'z', 53: 'x', 54: 'c', 55: 'v', 56: 'b', 57: 'n', 58: 'm', 59: ',', 60: '.', 61: '/', 65: '<SPACE>', 111: '<UPARROW>', 113: '<LEFTARROW>', 116: '<DOWNARROW>', 114: '<RIGHTARROW>', 110: '<HOME>', 115: '<END>', 112: '<PGUP>', 117: '<PGDN>', 77: '<NUMLOCK>', 106: '<NUM/>', 63: '<NUM*>', 82: '<NUM->', 79: '<NUM7>', 80: '<NUM8>', 81: '<NUM9>', 83: '<NUM4>', 84: '<NUM5>', 85: '<NUM6>', 86: '<NUM+>', 87: '<NUM1>', 88: '<NUM2>', 89: '<NUM3>', 90: '<NUM0>', 91: '<NUM.>', 104: '<NUMENTER>', 134: '<RWIN>', 133: '<LWIN>'}

lst = []
f = open('key','r').readlines()
for i in f:
    # print(i.split())
    data = i.split()
    if len(data) == 1:
        lst.append(data[0])
print(lst)

for i in lst:
    if keymap.get(int(i)):
        if keymap[int(i)] == '<SPACE>':
print(' ',end='')
        else:
print(keymap[int(i)],end='')

flag = [75,67,83,67,123,103,48,48,100,95,108,117,99,107,95]
print()
for i in flag:
    print(chr(i),end='')

Output:

1
2
3
['50', '40', '26', '54', '31', '58', '38', '46', '65', '41', '46', '38', '42', '65', '57', '26', '50', '47', '65', '16', '14', '65', '15', '16', '65', '17', '12', '65', '15', '16', '65', '10', '11', '12', '65', '10', '19', '12', '65', '13', '17', '65', '13', '17', '65', '10', '19', '19', '65', '18', '14', '65', '10', '19', '17', '65', '10', '10', '16', '65', '18', '18', '65', '10', '19', '16', '65', '18', '14', '65', '43', '26', '28', '65', '33', '43', '38', '57', '65', '10', '65', '27', '32', '31']
decimal flag ne; 75 67 83 67 123 103 48 48 100 95 108 117 99 107 95 het phan 1 roi
KCSC{g00d_luck_

For mouse, I used python plot to redraw mouse movement. Pay attention to a[0] and a[1] values, they are coordinates, not the length when moving like USB protocol.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
import matplotlib.pyplot as plt

f = open("mouse", "r").readlines()

mouseX = []
mouseY = []
X = 0
Y = 0
for i in range(len(f)):
    # print(f[i].split()[1].split('=')[1])
    x = f[i].split()[1].split('=')[1]
    y = f[i].split()[2].split('=')[1]
    X = int(x)
    Y = int(y)
    mouseX.append(X)
    mouseY.append(-Y)

# out = open("click_coordinates.txt", "w")

# plt.plot(mouseX,mouseY)
plt.scatter(mouseX, mouseY, c='r', marker = '.')
plt.show()

Last part of the flag:

/images/writeups/kcsc/flag3.png
Flag
FLAG:

KCSC{g00d_luck_have_fuN_1337}