Skip to content

Instantly share code, notes, and snippets.

@jborean93
Last active October 12, 2022 19:44
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save jborean93/246daf83f3aaca631dbca5652c3cc91e to your computer and use it in GitHub Desktop.
Save jborean93/246daf83f3aaca631dbca5652c3cc91e to your computer and use it in GitHub Desktop.
Gets the SMB2 Application Key from a Logon Session
# Copyright: (c) 2022, Jordan Borean (@jborean93) <jborean93@gmail.com>
# 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