Last active
October 12, 2022 19:44
-
-
Save jborean93/246daf83f3aaca631dbca5652c3cc91e to your computer and use it in GitHub Desktop.
Gets the SMB2 Application Key from a Logon Session
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Copyright: (c) 2022, Jordan Borean (@jborean93) <[email protected]> | |
# MIT License (see LICENSE or https://opensource.org/licenses/MIT) | |
<# Example Code to Run on the Server | |
$pipeServer = [System.IO.Pipes.NamedPipeServerStream]::new("jordan-test", [System.IO.Pipes.PipeDirection]::InOut) | |
$pipeServer.WaitForConnection() | |
try { | |
$tokenStat = Get-NamedPipeClientStatistics -Pipe $pipeServer | |
$appKey = Get-SMBApplicationKey -LogonId $tokenStat.AuthenticationId | |
[System.Convert]::ToBase64String($appKey.Applicationkey) | |
} | |
finally { | |
$pipeServer.Dispose() | |
} | |
#> | |
<# Example Code to Run as a Client - Python | |
import base64 | |
import smbclient | |
pipe = smbclient.open_file(r'\\server2022.domain.test\IPC$\jordan-test', mode='rb', buffering=0, file_type='pipe') | |
app_key = pipe.fd.tree_connect.session.application_key | |
print(base64.b64encode(app_key).decode()) | |
pipe.close() | |
#> | |
$addParams = @{ | |
TypeDefinition = @' | |
using Microsoft.Win32.SafeHandles; | |
using System; | |
using System.ComponentModel; | |
using System.IO; | |
using System.Runtime.InteropServices; | |
using System.Security.AccessControl; | |
using System.Security.Principal; | |
namespace Win32 | |
{ | |
[Flags] | |
public enum FileFlags : uint | |
{ | |
None = 0x00000000, | |
OpenNoRecall = 0x00100000, | |
OpenReparsePoint = 0x00200000, | |
SessionAware = 0x00800000, | |
PosixSemantics = 0x01000000, | |
BackupSemantics = 0x02000000, | |
DeleteOnClose = 0x04000000, | |
SequentialScan = 0x08000000, | |
RandomAccess = 0x10000000, | |
NoBuffering = 0x20000000, | |
Overlapped = 0x40000000, | |
WriteThrough = 0x80000000, | |
} | |
[Flags] | |
public enum NativeFileAccess : uint | |
{ | |
ReadData = 0x00000001, | |
WriteData = 0x00000002, | |
AppendData = 0x00000004, | |
ReadEA = 0x00000008, | |
WriteEA = 0x00000010, | |
Execute = 0x00000020, | |
DeleteChild = 0x00000040, | |
ReadAttributes = 0x00000080, | |
WriteAttributes = 0x00000100, | |
Delete = 0x00010000, | |
ReadControl = 0x00020000, | |
WriteDAC = 0x00040000, | |
WriteOwner = 0x00080000, | |
Synchronize = 0x00100000, | |
AccessSystemSecurity = 0x01000000, | |
MaximumAllowed = 0x02000000, | |
GenericAll = 0x10000000, | |
GenericExecute = 0x20000000, | |
GenericWrite = 0x40000000, | |
GenericRead = 0x80000000, | |
} | |
public class NativeHelpers | |
{ | |
[StructLayout(LayoutKind.Sequential)] | |
public struct IO_STATUS_BLOCK | |
{ | |
public UInt32 Status; | |
public UIntPtr Information; | |
} | |
[StructLayout(LayoutKind.Sequential)] | |
public struct LUID | |
{ | |
public UInt32 LowPart; | |
public Int32 HighPart; | |
public static explicit operator Int64(LUID luid) | |
{ | |
return ((Int64)luid.HighPart) << 32 | luid.LowPart; | |
} | |
} | |
[StructLayout(LayoutKind.Sequential)] | |
public struct TOKEN_STATISTICS | |
{ | |
public LUID TokenId; | |
public LUID AuthenticationId; | |
public UInt64 ExpirationTime; | |
public UInt32 TokenType; | |
public UInt32 ImpersonationLevel; | |
public UInt32 DynamicCharged; | |
public UInt32 DynamicAvailable; | |
public UInt32 GroupCount; | |
public UInt32 PrivilegeCount; | |
public LUID ModifiedId; | |
} | |
public enum TOKEN_INFORMATION_CLASS | |
{ | |
TokenUser = 1, | |
TokenGroups, | |
TokenPrivileges, | |
TokenOwner, | |
TokenPrimaryGroup, | |
TokenDefaultDacl, | |
TokenSource, | |
TokenType, | |
TokenImpersonationLevel, | |
TokenStatistics, | |
TokenRestrictedSids, | |
TokenSessionId, | |
TokenGroupsAndPrivileges, | |
TokenSessionReference, | |
TokenSandBoxInert, | |
TokenAuditPolicy, | |
TokenOrigin, | |
TokenElevationType, | |
TokenLinkedToken, | |
TokenElevation, | |
TokenHasRestrictions, | |
TokenAccessInformation, | |
TokenVirtualizationAllowed, | |
TokenVirtualizationEnabled, | |
TokenIntegrityLevel, | |
TokenUIAccess, | |
TokenMandatoryPolicy, | |
TokenLogonSid, | |
TokenIsAppContainer, | |
TokenCapabilities, | |
TokenAppContainerSid, | |
TokenAppContainerNumber, | |
TokenUserClaimAttributes, | |
TokenDeviceClaimAttributes, | |
TokenRestrictedUserClaimAttributes, | |
TokenRestrictedDeviceClaimAttributes, | |
TokenDeviceGroups, | |
TokenRestrictedDeviceGroups, | |
TokenSecurityAttributes, | |
TokenIsRestricted, | |
MaxTokenInfoClass | |
} | |
} | |
public class NativeMethods | |
{ | |
[DllImport("Kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] | |
private static extern SafeFileHandle CreateFileW( | |
string lpFileName, | |
NativeFileAccess dwDesiredAccess, | |
FileShare dwShareMode, | |
IntPtr lpSecurityAttributes, | |
FileMode dwCreationDisposition, | |
UInt32 dwFlagsAndAttributes, | |
IntPtr hTemplateFile); | |
public static SafeFileHandle CreateFileW(string path, NativeFileAccess desiredAccess, FileShare shareMode, | |
FileMode creationDisposition, FileAttributes attributes, FileFlags flags) | |
{ | |
UInt32 flagsAndAttrs = (UInt32)attributes | (UInt32)flags; | |
SafeFileHandle handle = CreateFileW(path, desiredAccess, shareMode, IntPtr.Zero, | |
creationDisposition, flagsAndAttrs, IntPtr.Zero); | |
if (handle.IsInvalid) | |
throw new Win32Exception(); | |
return handle; | |
} | |
[DllImport("Kernel32.dll", EntryPoint = "GetCurrentThread")] | |
private static extern IntPtr NativeGetCurrentThread(); | |
public static SafeProcessHandle GetCurrentThread() | |
{ | |
// Mark as not an owner so the handle isn't closed (it shouldn't be closed) | |
return new SafeProcessHandle(NativeGetCurrentThread(), false); | |
} | |
[DllImport("Advapi32.dll", SetLastError = true)] | |
private unsafe static extern bool GetTokenInformation( | |
SafeAccessTokenHandle TokenHandle, | |
NativeHelpers.TOKEN_INFORMATION_CLASS TokenInformationClass, | |
byte* TokenInformation, | |
int TokenInformationLength, | |
out UInt32 ReturnLength); | |
public static NativeHelpers.TOKEN_STATISTICS GetTokenStatistics(SafeAccessTokenHandle token) | |
{ | |
NativeHelpers.TOKEN_STATISTICS tokenStats = new NativeHelpers.TOKEN_STATISTICS(); | |
int bufferLength = Marshal.SizeOf<NativeHelpers.TOKEN_STATISTICS>(); | |
uint returnLength; | |
unsafe | |
{ | |
if (!GetTokenInformation(token, NativeHelpers.TOKEN_INFORMATION_CLASS.TokenStatistics, (byte *)&tokenStats, | |
bufferLength, out returnLength)) | |
{ | |
throw new Win32Exception(); | |
} | |
} | |
return tokenStats; | |
} | |
[DllImport("Advapi32.dll", EntryPoint = "ImpersonateNamedPipeClient", SetLastError = true)] | |
private static extern bool NativeImpersonateNamedPipeClient( | |
SafePipeHandle hNamedPipe); | |
public static void ImpersonateNamedPipeClient(SafePipeHandle pipe) | |
{ | |
if (!NativeImpersonateNamedPipeClient(pipe)) | |
{ | |
throw new Win32Exception(); | |
} | |
} | |
[DllImport("ntdll.dll", EntryPoint = "NtFsControlFile")] | |
private unsafe static extern UInt32 NativeNtFsControlFile( | |
SafeFileHandle hDevice, | |
IntPtr Event, | |
IntPtr ApcRoutine, | |
IntPtr ApcContext, | |
ref NativeHelpers.IO_STATUS_BLOCK IoStatusBlock, | |
UInt32 FsControlCode, | |
byte* InputBuffer, | |
Int32 InputBufferLength, | |
byte* OutputBuffer, | |
Int32 OutputBufferLength); | |
public static UIntPtr NtFsControlFile(SafeFileHandle device, UInt32 controlCode, byte[] inputBuffer, | |
byte[] outputBuffer) | |
{ | |
NativeHelpers.IO_STATUS_BLOCK ioStatusBlock = new NativeHelpers.IO_STATUS_BLOCK(); | |
unsafe | |
{ | |
fixed (byte* inputPtr = inputBuffer, outputPtr = outputBuffer) | |
{ | |
UInt32 res = NativeNtFsControlFile(device, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, ref ioStatusBlock, | |
controlCode, inputPtr, inputBuffer.Length, outputPtr, outputBuffer.Length); | |
if (res != 0) | |
{ | |
throw new Win32Exception(RtlNtStatusToDosError(res)); | |
} | |
} | |
} | |
return ioStatusBlock.Information; | |
} | |
[DllImport("Advapi32.dll", EntryPoint = "OpenThreadToken", SetLastError = true)] | |
private static extern bool NativeOpenThreadToken( | |
SafeProcessHandle ThreadHandle, | |
TokenAccessLevels DesiredAccess, | |
bool OpenAsSelf, | |
out SafeAccessTokenHandle TokenHandle); | |
public static SafeAccessTokenHandle OpenThreadToken(SafeProcessHandle thread, TokenAccessLevels access, | |
bool asSelf) | |
{ | |
SafeAccessTokenHandle token; | |
if (!NativeOpenThreadToken(thread, access, asSelf, out token)) | |
{ | |
throw new Win32Exception(); | |
} | |
return token; | |
} | |
[DllImport("Advapi32.dll", EntryPoint = "RevertToSelf", SetLastError = true)] | |
private static extern bool NativeRevertToSelf(); | |
public static void RevertToSelf() | |
{ | |
if (!NativeRevertToSelf()) | |
{ | |
throw new Win32Exception(); | |
} | |
} | |
[DllImport("ntdll.dll")] | |
private static extern Int32 RtlNtStatusToDosError( | |
UInt32 Status); | |
} | |
} | |
'@ | |
} | |
$addTypeCommand = Get-Command -Name Add-Type | |
if ('CompilerParameters' -in $addTypeCommand.Parameters.Keys) { | |
$referencedAssemblies = @( | |
[System.ComponentModel.Win32Exception].Assembly.Location, | |
[Microsoft.Win32.SafeHandles.SafePipeHandle].Assembly.Location | |
) | |
$addParams.CompilerParameters = [CodeDom.Compiler.CompilerParameters]@{ | |
CompilerOptions = "/unsafe -reference:$($referencedAssemblies -join ",")" | |
} | |
} | |
else { | |
$addParams.CompilerOptions = '/unsafe' | |
} | |
Add-Type @addParams | |
Function Get-NamedPipeClientStatistics { | |
<# | |
.SYNOPSIS | |
Gets the logon session statistics of a pipe client. | |
.DESCRIPTION | |
Gets the logon session TOKEN_STATISTICS of the client connection to the named pipe. | |
These details can be used to retrieve the logon session (AuthenticatonId) of the logon session in LSA. | |
.PARAMETER Pipe | |
The named pipe that has a client connected to it. | |
.EXAMPLE | |
$pipeServer = [System.IO.Pipes.NamedPipeServerStream]::new( | |
"my-pipe", | |
[System.IO.Pipes.PipeDirection]::InOut) | |
$pipeServer.WaitForConnection() | |
$tokenStat = Get-NamedPipeClientStatistics -Pipe $pipeServer | |
$pipeServer.Dispose() | |
#> | |
[OutputType([Win32.NativeHelpers+TOKEN_STATISTICS])] | |
[CmdletBinding()] | |
param ( | |
[Parameter(Mandatory)] | |
[System.IO.Pipes.NamedPipeServerStream] | |
$Pipe | |
) | |
try { | |
[Win32.NativeMethods]::ImpersonateNamedPipeClient($Pipe.SafePipeHandle) | |
try { | |
$currentThread = [Win32.NativeMethods]::OpenThreadToken( | |
[Win32.NativeMethods]::GetCurrentThread(), | |
[System.Security.Principal.TokenAccessLevels]::Query, | |
$true) | |
} | |
finally { | |
[Win32.NativeMethods]::RevertToSelf() | |
} | |
try { | |
[Win32.NativeMethods]::GetTokenStatistics($currentThread) | |
} | |
finally { | |
$currentThread.Dispose() | |
} | |
} | |
catch { | |
$_.ErrorDetails = "Failed to get named pipe client TOKEN_STATISTICS: $_" | |
$PSCmdlet.WriteError($_) | |
} | |
} | |
Function Get-SMBApplicationKey { | |
<# | |
.SYNOPSIS | |
Get the SMB Application Key for a logon session. | |
.DESCRIPTION | |
Gets the SMB Application Key that is calculated by a client's logon session. | |
This key is derived from the SSPI/GSSAPI authentication session key and is considered a secret. | |
.PARAMETER LogonId | |
The logon LUID as an Int64 that represents the client's logon session. | |
.EXAMPLE | |
Get-SMBApplicationKey -LogonId 1234 | |
.NOTES | |
This is the implementation of https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/7d7669e3-f9cf-443b-b5a4-19451b4e3378. | |
#> | |
[OutputType("SMBApplicationKey")] | |
[CmdletBinding()] | |
param ( | |
[Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)] | |
[Int64[]] | |
$LogonId | |
) | |
begin { | |
try { | |
$smbDeviceDriver = [Win32.NativeMethods]::CreateFileW( | |
"\\?\GLOBALROOT\Device\Srv2", | |
[Win32.NativeFileAccess]"GenericWrite, GenericRead", | |
[System.IO.FileShare]::Read, | |
[System.IO.FileMode]::Open, | |
[System.IO.FileAttributes]::Normal, | |
[Win32.FileFlags]::None) | |
} | |
catch { | |
$PSCmdlet.ThrowTerminatingError($_) | |
} | |
} | |
process { | |
# This is undocumented but sending this control code to the device driver gets the SMB driver to | |
# return the application key in the output buffer | |
# DeviceType = 0x0014 - FILE_DEVICE_NETWORK_FILE_SYSTEM | |
# Access = 0x01 - FILE_READ_ACCESS | |
# Function = 0x000E - Unknown? | |
# Method = 0x00 - METHOD_BUFFERED | |
$controlCode = 0x00144038 | |
foreach ($id in $LogonId) { | |
try { | |
$inputBuffer = [System.BitConverter]::GetBytes($id) | |
$outputBuffer = [byte[]]::new(16) | |
$null = [Win32.NativeMethods]::NtFsControlFile( | |
$smbDeviceDriver, | |
$controlCode, | |
$inputBuffer, | |
$outputBuffer) | |
[PSCustomObject]@{ | |
PSTypeName = "SMBApplicationKey" | |
LogonId = $id | |
Applicationkey = $outputBuffer | |
} | |
} | |
catch { | |
$_.ErrorDetails = "Failed to retrieved SMB Application Key for Logon $($id): $_" | |
$PSCmdlet.WriteError($_) | |
} | |
} | |
} | |
end { | |
$smbDeviceDriver.Dispose() | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment