[CTF NDH 2018 Quals] Write-Up – Reverse : SoStealthy

03
avril
2018
  • Google Plus
  • LinkedIn
  • Viadeo
Posted by: Yann C.  /   Category: CTF / Events / NDH / NDH2k18 / Reverse-Engineering   /   Aucun commentaire

Présentation d’un write-up de résolution du challenge « Reverse – SoStealthy » des qualifications du CTF de la Nuit du Hack 2018.

Le weekend du 31/03/2018 se déroulait les pré-qualifications pour la Nuit du Hack 2018 sous forme d’un CTF Jeopardy. Ayant eu l’occasion et le temps d’y participer avec quelques collègues et amis, voici un write-up de résolution d’un des challenges auquel nous avons pu participer.

  • Catégorie : Reverse
  • Nom : SoStealthy
  • Description : During an incident response, we captured the network traffic from a suspected compromised host. Could you help us reverse the installed malware?
  • File : suspicious.pcap (5.80MB – 7b34f24ad1a87204bc5b1aa4044013c270e171d89d07c1eab0f24e9e2cc5498b)
  • Points : 150

Pour ce challenge, une fichier PCAP suspicieux est fourni. Celui-ci comprend de nombreuses requêtes HTTP. Nous débutons pas l’extraction des objets HTTP depuis Wireshark :

Wireshark extract HTTP objets

Wireshark extract HTTP objets

Ces objets sont nombreux, il va falloir analyser cet export :

HTTP object list

HTTP object list

Parmi les différents fichiers exportés, l’un d’eux attire l’attention. Un fichier « favicon » contenant du code :

Suspicious favicon

Suspicious favicon

Ce fichier dispose d’un en-tête XML puis d’un code JavaScript chargeant un ActiveX. La charge utile de cet ActiveX est encodée en base64, difficile de faire plus suspicieux.

JScript header

JScript header

JScript footer

JScript footer

En nettoyant l’en-tête XML et le footer XML associé, nous nous retrouvons avec un script JScript exploitable via les binaires Windows « cscript.exe » ou « wscript.exe » :

JScript cleaned

JScript cleaned

A l’exécution de celui-ci, une fenêtre GUI avec un formulaire nous est proposée :

Say the magic word

Say the magic word

Clairement, il nous faut à présent désosser ce binaire pour découvrir le « magic word » souhaité.

La décompilation de la version JScript avec IDA et le debugging de ce script avec la commande « cscript //X » avec Visual Studio ne donnent rien. La piste de faire un dump à chaud du process « cscript.exe » ou encore de la VM pour une analyse par volatility n’ont pas été concluantes non-plus.

Le nom de James Forshaw et DotNetToJscript nous révèle la manière dont ce binaire a été converti / embarqué dans du JScript : le projet Github DotNetToJscript permet de convertir un code .NET en JScript.

D’après les observations, le code semble en conséquence être du .NET, et très certainement une version « Portable Executable » (PE), creusons cette piste.

Les PE disposent généralement d’un header reconnaissable « MZ », header que l’on trouve mais pas à l’offset « 0 » si l’on décode le base64 :

PE header

PE header

PE header identification

PE header identification

On nettoie le début du code base64 décodée pour débuter avec le header « MZ » valide, et enregistrons le binaire résultant en tant que « .exe ».

Clean header to get PE file

Clean header to get PE file

Ce binaire peut à présent être chargé dans « dnSpy« , pour dé-compiler le code .NET de manière intelligible.

On repère rapidement les fonctions du formulaire .NET, et notamment une opération XOR :

XOR in .NET

XOR in .NET

    // Token: 0x06000005 RID: 5 RVA: 0x0000226C File Offset: 0x0000046C
    private bool MeeBish0iotho9biBuJi(string magicWord)
    {
        for (int i = 0; i < this.Tai8Aip0ua3ULi6zo1je.Length; i++)
        {
            uint num = (uint)(magicWord[i] ^ this.Tai8Aip0ua3ULi6zo1je[i]);
            bool flag = (ulong)num != (ulong)((long)this.az5nieghahj0Iekah0ph[i]);
            if (flag)
            {
                return false;
            }
        }
        return true;
    }

La clé du XOR est stockée en décimal dans le code dé-compilé :

XOR key

XOR key

    private int[] az5nieghahj0Iekah0ph = new int[]
    {
        21,
        91,
        20,
        0,
        126,
        0,
        61,
        24,
        2,
        82,
        7,
        17,
        88,
        22,
        18,
        21,
        114,
        117,
        15,
        80,
        59,
        24
    };

La chaîne entrée dans le formulaire GUI est XORée avec les 22 derniers bytes du payload base64 lui-même :

22 last bytes of it's own code

22 last bytes of it’s own code

Run the ActiveX with his own code in input

Run the ActiveX with his own code in input

Les 22 derniers caractères sont donc :

FkKEJ5dGVbXSkIAAAACgsA

La clé XOR est en décimal :

key = [21,91,20,0,126,0,61,24,2,82,7,17,88,22,18,21,114,117,15,80,59,24]

Pour trouver l’inconnue « magic word », il nous suffit donc de XORer les deux :

def xor_strings(xs, ys):
 return "".join(chr(ord(x) ^ ord(y)) for x, y in zip(xs, ys))

last22bytes = "FkKEJ5dGVbXSkIAAAACgsA"
xorKey = [21,91,20,0,126,0,61,24,2,82,7,17,88,22,18,21,114,117,15,80,59,24]

xorKeyStr = ""
for c in xorKey:
 xorKeyStr = xorKeyStr + str(unichr(c))

print "Magic Word : NDH{" + xor_strings(last22bytes, xorKeyStr) + "}"

Exécution :

XOR to get the flag

XOR to get the flag

 

Flag : NDH{S0_E45Y_T0_B3_ST34L7HY}

Ce challenge nous aura résisté une bonne partie de la nuit ! Chapeau à Estelle, Martin, Georges et Timothée pour celui-ci 🙂

Salutations à toute l’équipe, on remet ça quand vous voulez 😉 // Gr3etZ

  • Google Plus
  • LinkedIn
  • Viadeo
Author Avatar

About the Author : Yann C.

Consultant en sécurité informatique et s’exerçant dans ce domaine depuis le début des années 2000 en autodidacte par passion, plaisir et perspectives, il maintient le portail ASafety pour présenter des articles, des projets personnels, des recherches et développements, ainsi que des « advisory » de vulnérabilités décelées notamment au cours de pentest.