C#でWindows認証ユーザの偽装を試してみた時のメモ

# はじめに
以前、非ドメイン参加のPC(Windows 7 Pro)からドメイン参加のサーバ(Windows Server 2008 R2)へnet useして接続するとイベントログ（Security）にログオン失敗（EventType = '16' And EventID = '4625'）が出力されて困ったことがあった。
いろいろ調べてみたら「認証ユーザーを偽装」というのがあって、これを使ってみたところ無事にログイン失敗が記録されずに済みました。
また、net useと違い「85 ローカル デバイス名は既に使用されています。」に悩まされることもないですね。

# ソースコード

DLLを作成する場合は、「クラスライブラリ」としてプロジェクトを作成します。

```csharp:Projects\WinLogon\WinLogon\ClsLogon.cs
using System;
using System.Text;
using System.Runtime.InteropServices;   // DLL
using System.Security.Principal;

namespace WinLogon
{
  public class ClsLogon
  {
    [DllImport("advapi32.dll", SetLastError = true)]
    public static extern bool LogonUser(
        String lpszUserName,
        String lpszDomain,
        String lpszPassword,
        LogonSessionType dwLogonType,
        LogonProvider dwLogonProvider,
        out IntPtr phToken);

    [DllImport("advapi32.dll", SetLastError = true)]
    public static extern bool DuplicateToken(
        IntPtr existingTokenHandle,
        int SECURITY_IMPERSONATION_LEVEL,
        ref IntPtr duplicateTokenHandle);

    [DllImport("kernel32.dll", SetLastError = true)]
    public extern static bool CloseHandle(IntPtr handle);

    public enum LogonSessionType : uint
    {
      Interactive = 2,
      Network,
      Batch,
      Service,
      NetworkCleartext = 8,
      NewCredentials
    }
    public enum LogonProvider : uint
    {
      Default = 0,
      WinNT35,
      WinNT40,
      WinNT50
    }

    private WindowsImpersonationContext _objContext = null;
    private String _strUserName = null;
    private String _strPassword = null;
    private String _strDomainName = null;
    private String _strMessage = "";
    private IntPtr _objUserHandle = IntPtr.Zero;
    private IntPtr _objDupUserHandle = IntPtr.Zero;

    public ClsLogon()
    {
    }

    public String strUserName { get { return _strUserName; } set { _strUserName = value; } }
    public String strPassword { get { return _strPassword; } set { _strPassword = value; } }
    public String strDomainName { get { return _strDomainName; } set { _strDomainName = value; } }
    public String strMessage { get { return _strMessage; } set { _strMessage = value; } }

    public Boolean Logon()
    {
      ClsWinError objWinError = new ClsWinError();
      Boolean blnIsOk = false;
      int intWin32Error = 0;
      String strErrDesc = null;
      _strMessage = "NG";
      try
      {
        blnIsOk = LogonUser(_strUserName, _strDomainName, _strPassword, LogonSessionType.NewCredentials, LogonProvider.Default, out _objUserHandle);
        if (blnIsOk)
        {
          blnIsOk = DuplicateToken(_objUserHandle, 2, ref _objDupUserHandle);
          if (blnIsOk)
          {
            WindowsIdentity identity = new WindowsIdentity(_objDupUserHandle);
            WindowsImpersonationContext _objContext = identity.Impersonate();
            _strMessage = "OK";
          }
          else
          {
            intWin32Error = Marshal.GetLastWin32Error();
          }
        }
        else
        {
          intWin32Error = Marshal.GetLastWin32Error();
        }
      }
      catch (Exception e)
      {
        _strMessage = "NG : SU(EXCEPTION) : " + e.Message;
      }
      if (0 != intWin32Error) strErrDesc = objWinError.GetWinErrMessage(intWin32Error);
      _strMessage += " : SU(" + intWin32Error + ")";
      if (!String.IsNullOrEmpty(strErrDesc)) _strMessage += " : " + strErrDesc;
      return blnIsOk;
    }

    public Boolean Logoff()
    {
      Boolean blnIsOk = true;
      try
      {
        if (null != _objContext) _objContext.Undo();
        if (IntPtr.Zero != _objDupUserHandle) CloseHandle(_objDupUserHandle);
        if (IntPtr.Zero != _objUserHandle) CloseHandle(_objUserHandle);
        _strMessage = "OK : EXIT SU(0)";
      }
      catch (Exception e)
      {
        blnIsOk = false;
        _strMessage = "NG : EXIT SU(EXCEPTION) : " + e.Message;
      }
      return blnIsOk;
    }
  }
}
```

```csharp:Projects\WinLogon\WinLogon\ClsWinError.cs
using System;
using System.Text;
using System.Runtime.InteropServices;
using System.Security.Principal;

namespace WinLogon
{
  class ClsWinError
  {
    [DllImport("kernel32.dll")]
    public static extern uint FormatMessage(uint dwFlags, IntPtr lpSource, uint dwMessageId, uint dwLanguageId, StringBuilder lpBuffer, int nSize, IntPtr Arguments);

    private const uint FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000;

    public ClsWinError()
    {
    }

    public String GetWinErrMessage(int intErrCode)
    {
      StringBuilder objSb = new StringBuilder(255);
      FormatMessage(
        FORMAT_MESSAGE_FROM_SYSTEM,
        IntPtr.Zero,
        (uint)intErrCode,
        0,
        objSb,
        objSb.Capacity,
        IntPtr.Zero);
      return objSb.ToString();
    }
  }
}
```
ついでにnet useクラスも作っておきます。

```csharp:Projects\WinLogon\WinLogon\ClsNetUse.cs
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;

namespace WinLogon
{
  public class ClsNetUse
  {
    [DllImport("mpr.dll", EntryPoint = "WNetCancelConnection2", CharSet = System.Runtime.InteropServices.CharSet.Unicode)]
    private static extern int WNetCancelConnection2(string lpName, Int32 dwFlags, bool fForce);

    [DllImport("mpr.dll", EntryPoint = "WNetAddConnection2", CharSet = System.Runtime.InteropServices.CharSet.Unicode)]
    private static extern int WNetAddConnection2(ref NETRESOURCE lpNetResource, string lpPassword, string lpUsername, Int32 dwFlags);

    public enum ResourceScope
    {
      RESOURCE_CONNECTED = 1,
      RESOURCE_GLOBALNET,
      RESOURCE_REMEMBERED,
      RESOURCE_RECENT,
      RESOURCE_CONTEXT
    };

    public enum ResourceType
    {
      RESOURCETYPE_ANY,
      RESOURCETYPE_DISK,
      RESOURCETYPE_PRINT,
      RESOURCETYPE_RESERVED = 8
    };

    [Flags]
    public enum ResourceUsage
    {
      RESOURCEUSAGE_CONNECTABLE = 0x00000001,
      RESOURCEUSAGE_CONTAINER = 0x00000002,
      RESOURCEUSAGE_NOLOCALDEVICE = 0x00000004,
      RESOURCEUSAGE_SIBLING = 0x00000008,
      RESOURCEUSAGE_ATTACHED = 0x00000010,
      RESOURCEUSAGE_ALL = (RESOURCEUSAGE_CONNECTABLE |
                           RESOURCEUSAGE_CONTAINER | RESOURCEUSAGE_ATTACHED),
    };

    public enum ResourceDisplayType
    {
      RESOURCEDISPLAYTYPE_GENERIC,
      RESOURCEDISPLAYTYPE_DOMAIN,
      RESOURCEDISPLAYTYPE_SERVER,
      RESOURCEDISPLAYTYPE_SHARE,
      RESOURCEDISPLAYTYPE_FILE,
      RESOURCEDISPLAYTYPE_GROUP,
      RESOURCEDISPLAYTYPE_NETWORK,
      RESOURCEDISPLAYTYPE_ROOT,
      RESOURCEDISPLAYTYPE_SHAREADMIN,
      RESOURCEDISPLAYTYPE_DIRECTORY,
      RESOURCEDISPLAYTYPE_TREE,
      RESOURCEDISPLAYTYPE_NDSCONTAINER
    };

    [Flags]
    public enum AddConnectionOptions
    {
      CONNECT_UPDATE_PROFILE = 0x00000001,
      CONNECT_UPDATE_RECENT = 0x00000002,
      CONNECT_TEMPORARY = 0x00000004,
      CONNECT_INTERACTIVE = 0x00000008,
      CONNECT_PROMPT = 0x00000010,
      CONNECT_NEED_DRIVE = 0x00000020,
      CONNECT_REFCOUNT = 0x00000040,
      CONNECT_REDIRECT = 0x00000080,
      CONNECT_LOCALDRIVE = 0x00000100,
      CONNECT_CURRENT_MEDIA = 0x00000200,
      CONNECT_DEFERRED = 0x00000400,
      CONNECT_RESERVED = unchecked((int)0xFF000000),
      CONNECT_COMMANDLINE = 0x00000800,
      CONNECT_CMD_SAVECRED = 0x00001000,
      CONNECT_CRED_RESET = 0x00002000
    }

    [StructLayout(LayoutKind.Sequential)]
    internal struct NETRESOURCE
    {
      public ResourceScope dwScope;
      public ResourceType dwType;
      public ResourceDisplayType dwDisplayType;
      public ResourceUsage dwUsage;
      [MarshalAs(UnmanagedType.LPWStr)]
      public string lpLocalName;
      [MarshalAs(UnmanagedType.LPWStr)]
      public string lpRemoteName;
      [MarshalAs(UnmanagedType.LPWStr)]
      public string lpComment;
      [MarshalAs(UnmanagedType.LPWStr)]
      public string lpProvider;
    }

    List<int> _listNetUseOkErrNo = new List<int>();
    private String _strPathDShare = null;
    private String _strDriveName = null;
    private String _strDomainName = null;
    private String _strUserName = "";
    private String _strPassword = "";
    private Boolean _blnIsLogonAlwaysOk = false;
    private String _strMessage = "";

    public List<int> listNetUseOkErrNo { get { return _listNetUseOkErrNo; } set { _listNetUseOkErrNo = value; } }
    public String strPathDShare { get { return _strPathDShare; } set { _strPathDShare = value; } }
    public String strDriveName { get { return _strDriveName; } set { _strDriveName = value; } }
    public String strUserName { get { return _strUserName; } set { _strUserName = value; } }
    public String strPassword { get { return _strPassword; } set { _strPassword = value; } }
    public String strDomainName { get { return _strDomainName; } set { _strDomainName = value; } }
    public String strMessage { get { return _strMessage; } set { _strMessage = value; } }
    public Boolean blnIsLogonAlwaysOk { get { return _blnIsLogonAlwaysOk; } set { _blnIsLogonAlwaysOk = value; } }

    public ClsNetUse()
    {
    }

    public Boolean Connect()
    {
      ClsWinError objWinError = new ClsWinError();
      Boolean blnIsOk = true;
      _strMessage = "NG";
      if (String.IsNullOrEmpty(_strPathDShare)) _strPathDShare = "パスが指定されていません。";
      NETRESOURCE objNetResource = new NETRESOURCE();
      objNetResource.dwScope = 0;
      objNetResource.dwType = ResourceType.RESOURCETYPE_DISK;
      objNetResource.dwDisplayType = 0;
      objNetResource.dwUsage = 0;
      objNetResource.lpLocalName = (String.IsNullOrEmpty(_strDriveName) ? "" : _strDriveName + ":");
      objNetResource.lpRemoteName = _strPathDShare;
      objNetResource.lpProvider = "";
      try
      {
        String strLogonUser = _strUserName;
        if (!String.IsNullOrEmpty(_strDomainName)) strLogonUser = _strDomainName + @"\" + _strUserName;
        int intReturn = WNetAddConnection2(ref objNetResource, _strPassword, strLogonUser, 0);
        switch (intReturn)
        {
          case 0:
            _strMessage = "OK";
            break;
          default:
            if (_blnIsLogonAlwaysOk)
            {
              _strMessage = "--";
            }
            else
            {
              if (_listNetUseOkErrNo.Count > 0)
              {
                if (_listNetUseOkErrNo.Contains(intReturn))
                {
                  _strMessage = "--";
                }
                else
                {
                  blnIsOk = false;
                  _strMessage = "NG";
                }
              }
              else
              {
                blnIsOk = false;
                _strMessage = "NG";
              }
            }
            break;
        }
        String strErrDesc = objWinError.GetWinErrMessage(intReturn);
        _strMessage += " : 接続(" + intReturn + ") => " + _strPathDShare;
        if (!String.IsNullOrEmpty(strErrDesc)) _strMessage += " : " + strErrDesc;
      }
      catch (Exception e)
      {
        if (_blnIsLogonAlwaysOk)
        {
          _strMessage = "-- : 接続(EXCEPTION) => " + _strPathDShare + " : " + e.Message;
        }
        else
        {
          blnIsOk = false;
          _strMessage = "NG : 接続(EXCEPTION) => " + _strPathDShare + " : " + e.Message;
        }
      }
      return blnIsOk;
    }

    public Boolean DisConnect()
    {
      ClsWinError objWinError = new ClsWinError();
      Boolean blnIsOk = true;
      _strMessage = "NG";
      if (String.IsNullOrEmpty(_strPathDShare)) _strPathDShare = "パスが指定されていません。";
      try
      {
        int intReturn = WNetCancelConnection2(_strPathDShare, 0, true);
        switch (intReturn)
        {
          case 0:
          case 2250:
            _strMessage = "OK";
            break;
          default:
            if (_blnIsLogonAlwaysOk)
            {
              _strMessage = "--";
            }
            else
            {
              if (_listNetUseOkErrNo.Count > 0)
              {
                if (_listNetUseOkErrNo.Contains(intReturn))
                {
                  _strMessage = "--";
                }
                else
                {
                  blnIsOk = false;
                  _strMessage = "NG";
                }
              }
              else
              {
                blnIsOk = false;
                _strMessage = "NG";
              }
            }
            break;
        }
        String strErrDesc = objWinError.GetWinErrMessage(intReturn);
        _strMessage += " : 切断(" + intReturn + ") => " + _strPathDShare;
        if (!String.IsNullOrEmpty(strErrDesc)) _strMessage += " : " + strErrDesc;
      }
      catch (Exception e)
      {
        if (_blnIsLogonAlwaysOk)
        {
          _strMessage = "-- : 切断(EXCEPTION) => " + _strPathDShare + " : " + e.Message;
        }
        else
        {
          blnIsOk = false;
          _strMessage = "NG : 切断(EXCEPTION) => " + _strPathDShare + " : " + e.Message;
        }
      }
      return blnIsOk;
    }

  }
}
```

# 実行確認（認証ユーザーを偽装）

まずは認証していない状態でリモートホストに接続してみます。

```bat
PS C:\> ls \\remotehost\home
ls : パス '\\remotehost\home' が存在しないため検出できません。
発生場所 行:1 文字:1
+ ls \\remotehost\home
+ ~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : ObjectNotFound: (\\remotehost\home:String) [Get-ChildItem], ItemNotFoundException
    + FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetChildItemCommand

PS C:\>
```

接続に失敗しました。
それでは認証ユーザーを偽装を実行してみます。

```bat
PS C:\> $strPathFDll = "C:\Tool\Infra\dll\WinLogon.dll"
PS C:\> [Reflection.Assembly]::LoadFile($strPathFDll) | out-null
PS C:\> $objLogon= New-Object WinLogon.ClsLogon
PS C:\> $objLogon.strDomainName = "remotehost"
PS C:\> $objLogon.strUserName = "ここにユーザ名"
PS C:\> $objLogon.strPassword = "ここにパスワード"
PS C:\> $objLogon.Logon()
True
PS C:\> Write-Output $objLogon.strMessage
OK : SU(0)
PS C:\>
```

再度リモートホストに接続してみます。

```bat
PS C:\> ls \\remotehost\home

    ディレクトリ: \\remotehost\home

Mode                LastWriteTime         Length Name
----                -------------         ------ ----
d-----       2015/10/24      0:40                folder1
d-----       2016/01/10     23:34                folder2

PS C:\>
```

今度は無事にアクセスできました。
最後に切断します。

```bat
PS C:\> $objLogon.Logoff()
True
PS C:\> Write-Output $objLogon.strMessage
OK : EXIT SU(0)
PS C:\>
```

# 実行確認（net use）

net use版も接続確認してみます。

最初に接続エントリが存在しないことを確認します。

```bat
PS C:\> net use
新しい接続は記憶されます。

一覧にエントリが存在しません。

PS C:\>
```

net useを実行します。

```bat
PS C:\> $strPathFDll = "C:\Tool\Infra\dll\WinLogon.dll"
PS C:\> [Reflection.Assembly]::LoadFile($strPathFDll) | out-null
PS C:\> $objNetUse= New-Object WinLogon.ClsNetUse
PS C:\> $objNetUse.strDomainName = "remotehost"
PS C:\> $objNetUse.strUserName = "ここにユーザ名"
PS C:\> $objNetUse.strPassword = "ここにパスワード"
PS C:\> $objNetUse.strPathDShare = "\\remotehost\home"
PS C:\> $objNetUse.Connect()
True
PS C:\> Write-Output $objNetUse.strMessage
OK : 接続(0) => \\remotehost\home : この操作を正しく終了しました。

PS C:\>
```

一覧にエントリが存在することを確認します。

```bat
PS C:\> net use
新しい接続は記憶されます。


ステータス  ローカル名 リモート名                ネットワーク名

-------------------------------------------------------------------------------
OK                     \\remotehost\home      Microsoft Windows Network
コマンドは正常に終了しました。

PS C:\>
```

リモートホストに接続してみます。

```bat
PS C:\> ls \\remotehost\home


    ディレクトリ: \\remotehost\home


Mode                LastWriteTime         Length Name
----                -------------         ------ ----
d-----       2015/10/24      0:40                folder1
d-----       2016/01/10     23:34                folder2


PS C:\>
```

無事にアクセスできました。
最後に切断します。

```bat
PS C:\> $objNetUse.DisConnect()
True
PS C:\> Write-Output $objNetUse.strMessage
OK : 切断(0) => \\remotehost\home : この操作を正しく終了しました。

PS C:\> net use
新しい接続は記憶されます。

一覧にエントリが存在しません。

PS C:\>
```