Hubert Hackin''
  • All posts
  • About
  • Our CTF

NSEC22 VFCrypter - Sat, May 28, 2022 - Barberousse

| Rev | Nsec22

VFCrypter

Part 1

We have a .vbe file. Wtf is this? file doesn’t know either

$ file VFCrypter/ogeliruhg.vbe
VFCrypter/ogeliruhg.vbe: data

Fortunately, binwalk does

binwalk VFCrypter/ogeliruhg.vbe

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             Windows Script Encoded Data (screnc.exe)

It turns out that screnc.exe is the Microsoft Script Encoder. Basically an obfuscator for VBS. Some brave souls have already created scripts to decode these

Running the script on our vbe gives us some very ugly vbs. After some prettifying, it looks like this:

Function print(S)
    WScript.Echo s
End Function

Function md5haShBytes(aBYtes)
    Dim MD5
    Set mD5 = CreateObject("System.Security.Cryptography.MD5CryptoServiceProvider")
    MD5.initialiZe()
    md5hashByTes = MD5.ComputeHasH_2((aBytes))
End Function

Function StringToUTfBytes(aStrinG)
    Dim uTf8
    Set UTf8 = CreateObject("System.Text.UTF8Encoding")
    StringToUTfBytes = UTF8.GetBytes_4(aString)
End Function

Function bytestoHex(abytes)
    Dim hExStr,x
    For X = 1 To lenb(abytES)
        heXStr = Hex(ascb(midb((ABytes),x,1)))
        If Len(hexSTr) = 1 Then HexStR = "0" & Hexstr End If
    byTesToHex = byTesTOHex & hexStr
	Next
End Function

Dim DomaiN_hash
Dim good
gooD = 1

Set WshSheLl = CreateObject("WScript.Shell")
STRUserDOmain = wsHshell.ExpAnDENvironmEnTStrings("%USERDOMAIN%")
If strUserDomaiN = "5444595F15F45DBA6AC80502424541CE" Then
	pRint("FLAG-" & ByTesTOHex(md5hashBytes(strinGTouTFByteS("rhbmjhb" & strUsErDomaiN))))
	Good = 1
End If
If Good = 1 Then outfiLE = WScript.CreateObject("Scripting.FileSystemObject").GeTSpecIalFolder(2) & "\liuhfleriuh.exe"
Set objFSO = CreateObject("Scripting.FileSystemObject")
Set obJfile = ObjFSO.CReatetexTFile(OUtFile,True)
objFile.Write [...]
objFile.close
Dim objShell,oExec
Set objshelL = WScript.CreateObject("wscript.shell")
Set oExec = obJshell.Exec(OutFIle)
End If

It checks if %USERDOMAIN% is equal to some value. If it is, the script gives us a flag, writes a PE file to <temp>\\liuhfleriuh.exe and executes it.

However, there are a few issues with the script:

  1. The good variable is always 1, so the PE file will be created whether or not the check passes. This is good for us.
  2. %USERDOMAIN is an environment variable that contains the name of the domain to which the current user is connected (or the computer name for a locall account). The maximum length of this name is 15 characters, so it can never be equal to 5444595F15F45DBA6AC80502424541CE (I got confirmation from the challenge designer that this was supposed to be the MD5 hash of the domain name but there was a mistake).

No matter, we can compute the flag from "FLAG-" + md5("rhbmjhb"+"5444595F15F45DBA6AC80502424541CE")

FLAG-9184c340318a1a4324a6147ed9826b66

Part 2

Opening up the PE file, we notice that it has a LOV0 and a LOV1 section. Looking at the entrypoint, this definitely looks packed; lets run it through DiE. It’s detected as “UPX (modified)”. We spent some time debugging and dumping memory sections before realizing. Could it be so simple?!:

$ sed s/LOV/UPX/g liuhfleriuh.exe > liuhfleriuh_UPX.exe && upx -d liuhfleriuh_UPX.exe

Yes, this works… now we can work with the unpacked executable.

At the beginning of the entry point, we notice something suspicious: a bunch of MOV instructions all overwriting EAX without using the values. Those values are in the ASCII range though:

vfcrypter flag 2

FLAG-2d72c22e22f2184792b51950172d8e9f

Askgod is telling us it’s time to decrypt the VFT now. Let’s do this!

Part 3

Before we get to the VFT encryption, let’s look at some funky things this executable does.

It manually resolves Windows API functions by traversing the InLoadOrderModuleList of the PEB (a linked list that contains loaded DLLs). This technique is commonly used by malware authors to hide the imports and avoid telltale calls to LoadLibraryA and GetProcAddress.

vfcrypter PEB

The desired API functions are identified by the FNV-1a hash of their names. To add insult to injury, each instance uses a unique, non-standard seed. This makes it harder to precalculate the expected values and identify what functions are used without executing the program.

vfcrypter FNV1A

Now, onto the challenge itself.

We identified a function that traverses the filesystem looking for specific patterns in file names. The one that’s interesting to us, is the handler for .vft files.

vfcrypter .VFT

This function performs some checks for other detected files, but since we’re analyzing this statically, we can just skip over them. Once all the checks pass, the VFT is encrypted with the following, custom, algorithm.

vfcrypter crypto

While the algorithm uses a few cryptographically secure random values (4 x 7bit values generated with RtlGenRandom) it’s still wonky enough that even I can break it.

  1. It uses the 4 first bytes of the file as the rest of the 8 byte “random” buffer.
  2. Each 8 byte ciphertext block is fed back into it and used as the key for the next block.
  3. The firts 8 bytes of the file are overwritten with the initial key.
  4. In a block of 8 bytes, the decryption of the byte at position i is only dependent on the bytes at that same position i in the previous blocks and the ith “random” bytes.

That means, if we find the random values, we can easily decrypt the file by using each block as the key for the next one!

Since I’m not that good with this cryptography stuff, I figured it would be much quicker to brute force the random values than it would be for me to understand how to recover them. Luckily the JPEG format has plenty of known artifacts.

Point 4 above allowed us to decrypt half of each block, using the values we knew, and identify the location of one such artifact. In our case, it was the URL “http://ns.adobe.com” at offset 0x1230. Now that we know the expected plaintext value of a full block, we simply brute force the random values until the bytes match. This is trivial since we only have 7 x 127 values to try.

The script to do this was lost to the abyss (and it was terrible, so I would be ashamed to share it anyway)

We recovered the 4 values: 0x5f, 0x13, 0x35, 0x3b

Now we can decrypt the VFT:

RAND = [0xff, 0xd8, 0xff, 0xe0, 0x5f, 0x13, 0x35, 0x3b]

def decrypt(rand, cipher):
    dec = bytearray(len(enc))
    for i in range(0, len(enc)-16, 8):
        dec[i+8] = enc[i] ^ rand[0] ^ enc[i+8]
        dec[i+9] = enc[i+1] ^ rand[1] ^ enc[i+9]
        dec[i+10] = enc[i+2] ^ rand[2] ^ enc[i+10]
        dec[i+11] = enc[i+3] ^ rand[3] ^ enc[i+11]
        dec[i+12] = enc[i+4] ^ rand[4] ^ enc[i+12]
        dec[i+13] = enc[i+5] ^ rand[5] ^ enc[i+13]
        dec[i+14] = enc[i+6] ^ rand[6] ^ enc[i+14]
        dec[i+15] = cipher[i+7] ^ rand[7] ^ cipher[i+15]
    return dec

with open("my.jpg.vft.enc", "rb") as f:
    cipher = f.read()

plain = decrypt(RAND, cipher)
with open("my.jpg.vft", "wb") as f:
    f.write(plain)

vfcrypter crypto

FLAG-0675c58cdc08de1de3eb4c2187c91b3

Back to Home


Hackez la Rue! | © Hubert Hackin'' | 2024-05-27 | theme hugo.386