SSPI (NTLM) による暗号化通信

SSPI (NTLM) による暗号化通信

SSPI を使った安全な通信 で概要を紹介したように、 SSPI を利用すると Windows に組み込まれている各種セキュリティパッケージ (Kerberos, NTLM, SSL, ...) を、同じような方法で利用することが出来ます。SSPI がその下層の各種プロトコルの詳細を隠します。 逆に言えば SSPI を利用することが出来れば、各種セキュリティプロトコルを同様の方法で利用することが出来ます。 このページでは NTLM セキュリティパッケージを利用して、クライアントとサーバー間でセキュリティコンテキストを確立し、 データを暗号化して送受信する方法を紹介します。

プログラムの概要

プログラムの概要は SSPI を使った安全な通信 で説明したとおりです。 最初に ハンドシェイク を行い、セキュリティコンテキストを作成します。 サーバー側では、このコンテキストを使うことでクライアントを偽装したり、メッセージを暗号化することが可能になります。

クライアントとサーバー間の通信のプロトコルとしては、データを送受信する際には、 データサイズを sizeof(ULONG) バイト分送信し、それに引き続きデータ本体を送信することとします。

従って例えば、aaaaa という 5 バイトのデータを送信する場合は、これから送信するデータのサイズである 00 00 00 05 というバイト数 (5バイト) データを送り、それに続いて、"aaaaa" というデータ本体を送るものとします。

またソケットの操作では当サイトで作った WinSock 用ヘルパー関数 を利用しています。

プログラム実行例

コードが長くなるので、以下にコードを示したクライアントプログラム ntlmclt.exe とサーバープログラム ntlmsrv.exe を用いた動作実験を先に記載します。

まず ntlmsrv.exe を起動します。(ご利用のセキュリティソフトによっては、接続がブロックされるので注意してください)

> ntlmsrv.exe
Listening...

IP アドレス 127.0.0.1 上の TCP ポート 3000 番で、待ち受け (LISTENING) を開始します。

次にクライアントプログラムを起動します。これは IP アドレス 127.0.0.1 の TCP ポート 3000 番へ接続を試みます。

サーバーに接続できると、処理が流れます。

出力されるデータを見てみましょう

クライアント側は次の通りです。

> ntlmclt.exe
Client socket has been created.
Successfully connected to the server.
InitializeSecurityContext returned: 90312
0000  4e 54 4c 4d 53 53 50 00:02 00 00 00 16 00 16 00  NTLMSSP.........
0010  38 00 00 00 35 c2 8a e2:cb b9 70 77 eb 70 51 cf  8...5.....pw.pQ.
0020  30 ac c6 01 00 00 00 00:78 00 78 00 4e 00 00 00  0.......x.x.N...
0030  06 00 71 17 00 00 00 0f:4b 00 45 00 49 00 53 00  ..q.....K.E.I.S.
0040  55 00 4b 00 45 00 4f 00:2d 00 48 00 50 00 02 00  U.K.E.O.-.H.P...
0050  16 00 4b 00 45 00 49 00:53 00 55 00 4b 00 45 00  ..K.E.I.S.U.K.E.
0060  4f 00 2d 00 48 00 50 00:01 00 16 00 4b 00 45 00  O.-.H.P.....K.E.
0070  49 00 53 00 55 00 4b 00:45 00 4f 00 2d 00 48 00  I.S.U.K.E.O.-.H.
0080  50 00 04 00 16 00 6b 00:65 00 69 00 73 00 75 00  P.....k.e.i.s.u.
0090  6b 00 65 00 6f 00 2d 00:68 00 70 00 03 00 16 00  k.e.o.-.h.p.....
00a0  6b 00 65 00 69 00 73 00:75 00 6b 00 65 00 6f 00  k.e.i.s.u.k.e.o.
00b0  2d 00 68 00 70 00 07 00:08 00 40 d8 34 25 37 68  -.h.p.....@.4%7h
00c0  c9 01 00 00 00 00                                ......
InitializeSecurityContext returned: 0
Raw Token (16 Bytes):
0000  01 00 00 00 bb 69 88 75:73 d4 34 4b 00 00 00 00  .....i.us.4K....
Raw Message (54 Bytes):
0000  9f cb ed 34 d5 d8 9a 7e:1e d1 f0 5f 8e 74 9f 73  ...4...~..._.t.s
0010  39 dc 00 9b 84 02 be 2b:87 f2 75 21 94 8a 37 75  9......+..u!..7u
0020  36 d8 ae d7 cc 5b 7c d0:aa a3 87 ad 9c 9c 26 6f  6....[|.......&o
0030  89 a5 88 09 ac c3                                ......
Raw Padding (0 Bytes):
(None)
Message from the server:
0000  82 b1 82 cc 83 81 83 62:83 5a 81 5b 83 57 82 cd  .......b.Z.[.W..
0010  20 6e 74 6c 6d 73 72 76:2e 65 78 65 20 82 aa 88   ntlmsrv.exe ...
0020  c3 8d 86 89 bb 82 b5 82:c4 91 97 90 4d 82 b5 82  ............M...
0030  dc 82 b5 82 bd 2e                                ......
このメッセージは ntlmsrv.exe が暗号化して送信しました.

ぐちゃっとしたデータが見えてますが、要はサーバーと NTLM のハンドシェイクを行い、 その結果、暗号化されたメッセージをサーバーから受け取り、それを複合化している様子を示しています。

サーバー側は次の通りです。

> ntlmsrv.exe
Listening...
Accepting a client.
0000  4e 54 4c 4d 53 53 50 00:01 00 00 00 b7 b2 08 e2  NTLMSSP.........
0010  09 00 09 00 33 00 00 00:0b 00 0b 00 28 00 00 00  ....3.......(...
0020  06 00 71 17 00 00 00 0f:4b 45 49 53 55 4b 45 4f  ..q.....KEISUKEO
0030  2d 48 50 57 4f 52 4b 47:52 4f 55 50              -HPWORKGROUP
AcceptSecurityContext - ss = 0x00090312
0000  4e 54 4c 4d 53 53 50 00:03 00 00 00 00 00 00 00  NTLMSSP.........
0010  58 00 00 00 00 00 00 00:58 00 00 00 00 00 00 00  X.......X.......
0020  58 00 00 00 00 00 00 00:58 00 00 00 00 00 00 00  X.......X.......
0030  58 00 00 00 00 00 00 00:58 00 00 00 35 c2 88 e2  X.......X...5...
0040  06 00 71 17 00 00 00 0f:26 9e 31 35 10 b4 a5 5d  ..q.....&.15...]
0050  8d d0 b3 74 e7 9d 9f 4b:                         ...t...K
AcceptSecurityContext - ss = 0x00000000
Token (16 Bytes):
0000  01 00 00 00 bb 69 88 75:73 d4 34 4b 00 00 00 00  .....i.us.4K....
Message (54 Bytes):
0000  9f cb ed 34 d5 d8 9a 7e:1e d1 f0 5f 8e 74 9f 73  ...4...~..._.t.s
0010  39 dc 00 9b 84 02 be 2b:87 f2 75 21 94 8a 37 75  9......+..u!..7u
0020  36 d8 ae d7 cc 5b 7c d0:aa a3 87 ad 9c 9c 26 6f  6....[|.......&o
0030  89 a5 88 09 ac c3                                ......
Padding (0 Bytes):

こちらも要は、クライアントとハンドシェイクを行い、暗号化したメッセージをクライアントに送っています。 「Message (54 Bytes)」 の下にダンプされているデータが、暗号化されたメッセージです。 クライアント側でもそれを受け取っていることがわかります。(9f cb... で始まるデータがそれです)

クライアント・プログラム

以下の内容を ntlmclt.cpp として保存します。

#include <winsock2.h>
#include <security.h>
#include <sspi.h>
#include <stdio.h>
#include "sockhelper.h"


///////////////////////////////////////////////////////////////////////////////


int main( int argc, char* argv[] ) {

     //
     // ネットワーク接続の準備
     //

     INT nRet;

     // WinSock ライブラリの初期化
     WSADATA wsaData;     
     WORD wRet = WSAStartup(WINSOCK_VERSION, &wsaData);
     if (wRet) {
          printf ("WSAStartup Failed. %d\n", wRet);
          return 1;
     }
     
     // サーバー接続用のソケットを作成する
     SOCKET hClientSocket = socket(AF_INET, SOCK_STREAM, 0);
     if(hClientSocket == INVALID_SOCKET) {
          printf(
               "socket Failed. "
               "WSAGetLastError = 0x%08x\n",
               WSAGetLastError());
          return 1;
     }

     printf("Client socket has been created.\n");

     // サーバーへ接続
     ULONG ulAddress = inet_addr("127.0.0.1");
     SOCKADDR_IN name;
     name.sin_family = AF_INET;
     name.sin_port = htons(3000); // ポート番号
     memmove(&name.sin_addr, &ulAddress, sizeof(ULONG));

     nRet = connect(hClientSocket, (SOCKADDR*) &name, sizeof(SOCKADDR_IN));

     if(nRet) {
          printf("connect Failed. WSAGetLastError = %u\n", WSAGetLastError());
          return 1;
     }

     printf("Successfully connected to the server.\n");

     // SSPI (NTLM) クレデンシャルを取得する
     
     CredHandle hCredential;
     TimeStamp tsExpiry;
     SECURITY_STATUS ss;

     ss = AcquireCredentialsHandle(
          NULL, 
          NTLMSP_NAME_A, //"NTLM", 
          SECPKG_CRED_BOTH,
          NULL, 
          NULL, 
          NULL, 
          NULL, 
          &hCredential, 
          &tsExpiry);

     if(ss != SEC_E_OK) {
          printf("AcquireCredentialsHandle Failed. ss = 0x%08x\n", ss);
          return 1;
     }

     // サーバーとのハンドシェイク

     SecBuffer secBufIn[1], secBufOut[1];
     SecBufferDesc secBufDescIn, secBufDescOut;
     BOOL bFirst = TRUE;
     CtxtHandle hContext;

     PBYTE pbBuff = NULL;
     ULONG ulSize = 0;

     ss = SEC_I_CONTINUE_NEEDED;

     while(ss == SEC_I_CONTINUE_NEEDED) {

          ULONG ulAttr = 0;

          if( bFirst ) {
               secBufDescIn.cBuffers = 0;
               secBufDescIn.ulVersion = SECBUFFER_VERSION;
               secBufDescIn.pBuffers = NULL;
          }
          else { // 二度目以降の呼び出し
               ULONG ulInSize;
               ReceiveData(hClientSocket, (PBYTE)&ulInSize, sizeof(ulInSize));

               pbBuff = (PBYTE) calloc(ulInSize, 1);
               if(NULL == pbBuff) {
                    printf("calloc Failed.\n");
                    return 1;
               }

               ReceiveData(hClientSocket, pbBuff, ulInSize);
               PrintHexDump(ulInSize, pbBuff);

               secBufIn[0].BufferType = SECBUFFER_TOKEN;
               secBufIn[0].cbBuffer = ulInSize;
               secBufIn[0].pvBuffer = pbBuff;
               secBufDescIn.cBuffers = 1;
               secBufDescIn.ulVersion = SECBUFFER_VERSION;
               secBufDescIn.pBuffers = secBufIn;
          }

          secBufOut[0].BufferType = SECBUFFER_TOKEN;
          secBufOut[0].cbBuffer = 0;
          secBufOut[0].pvBuffer = NULL;
          secBufDescOut.cBuffers = 1;
          secBufDescOut.ulVersion = SECBUFFER_VERSION;
          secBufDescOut.pBuffers = secBufOut;


          ss = InitializeSecurityContext(
               &hCredential, 
               bFirst ? NULL: &hContext,
               "localhost", 
               ulAttr | ISC_REQ_ALLOCATE_MEMORY | ASC_REQ_CONFIDENTIALITY, 
               0,
               SECURITY_NETWORK_DREP, 
               &secBufDescIn, 
               0, 
               &hContext, 
               &secBufDescOut, 
               &ulAttr, 
               NULL);

          printf("InitializeSecurityContext returned: %x\n", ss);

          bFirst = FALSE;

          if(secBufOut[0].cbBuffer != 0) {
               ULONG ulOutSize = secBufOut[0].cbBuffer;
               
               // データサイズを送る
               SendData(hClientSocket, (PBYTE) &ulOutSize, sizeof(ulOutSize));
               // データを送る
               SendData(hClientSocket, (PBYTE) secBufOut[0].pvBuffer, 
                    secBufOut[0].cbBuffer);
          
               // ASC_REQ_ALLOCATE_MEMORY フラグで割り当てられたバッファを Free.
               FreeContextBuffer(secBufOut[0].pvBuffer);
          }

          if(pbBuff) {
               free(pbBuff);
               pbBuff = NULL;
          }

     } // while SEC_I_CONTINUE_NEEDED


     //
     // サーバーからのメッセージの受け取りと復号化
     //

     PBYTE pSignature = NULL;
     PBYTE pMessage = NULL;
     PBYTE pPadding = NULL;
     SecBuffer secBufMsg[3];
     SecBufferDesc secBufDescMsg;

     // トークン (シグネチャ) の受け取り
     ReceiveData(hClientSocket, (PBYTE)&ulSize, sizeof(ulSize));
     if(ulSize > 0) {
          pSignature = (PBYTE) calloc(ulSize, 1);
          if(!pSignature) {
               printf("calloc Failed.\n");
               return 1;
          }
          ReceiveData(hClientSocket, pSignature, ulSize);

          printf("Raw Token (%u Bytes):\n", ulSize);
          PrintHexDump(ulSize, pSignature);

          secBufMsg[0].BufferType = SECBUFFER_TOKEN;
          secBufMsg[0].cbBuffer = ulSize;
          secBufMsg[0].pvBuffer = pSignature;
     }
     else {
          printf("Raw Token (%u Bytes):\n", ulSize);
          printf("(None)\n");

          secBufMsg[0].BufferType = SECBUFFER_TOKEN;
          secBufMsg[0].cbBuffer = 0;
          secBufMsg[0].pvBuffer = NULL;
     }


     // メッセージの受け取り
     ReceiveData(hClientSocket, (PBYTE)&ulSize, sizeof(ulSize));
     if(ulSize > 0) {
          pMessage = (PBYTE) calloc(ulSize, 1);
          if(!pMessage) {
               printf("calloc Failed.\n");
               return 1;
          }
          ReceiveData(hClientSocket, pMessage, ulSize);

          printf("Raw Message (%u Bytes):\n", ulSize);
          PrintHexDump(ulSize, pMessage);

          secBufMsg[1].BufferType = SECBUFFER_DATA;
          secBufMsg[1].cbBuffer = ulSize;
          secBufMsg[1].pvBuffer = pMessage;
     }
     else {
          printf("Raw Message (%u Bytes):\n", ulSize);
          printf("(None)\n");

          secBufMsg[1].BufferType = SECBUFFER_DATA;
          secBufMsg[1].cbBuffer = 0;
          secBufMsg[1].pvBuffer = NULL;
     }


     // パディングの受け取り
     ReceiveData(hClientSocket, (PBYTE)&ulSize, sizeof(ulSize));
     if(ulSize > 0) {
          pPadding = (PBYTE) calloc(ulSize, 1);
          if(!pPadding) {
               printf("calloc Failed.\n");
               return 1;
          }
          ReceiveData(hClientSocket, pPadding, ulSize);

          printf("Raw Padding (%u Bytes):\n", ulSize);
          PrintHexDump(ulSize, pPadding);

          secBufMsg[2].BufferType = SECBUFFER_PADDING;
          secBufMsg[2].cbBuffer = ulSize;
          secBufMsg[2].pvBuffer = pPadding;
     }
     else {
          printf("Raw Padding (%u Bytes):\n", ulSize);
          printf("(None)\n");
          secBufMsg[2].BufferType = SECBUFFER_PADDING;
          secBufMsg[2].cbBuffer = 0;
          secBufMsg[2].pvBuffer = NULL;
     }

     // バッファ・ディスクリプションの準備
     secBufDescMsg.cBuffers = 3;
     secBufDescMsg.ulVersion = SECBUFFER_VERSION;
     secBufDescMsg.pBuffers = secBufMsg;

     ULONG lQual = 0;
     
     // 復号化
     ss = DecryptMessage(&hContext, &secBufDescMsg, 0, &lQual);

     if(SEC_E_OK != ss) {
          printf("EncryptMessage Failed. ss = 0x%08x\n", ss);
          return 1;
     }


     //
     // 結果の表示
     //

     PBYTE pMessageFromServer = NULL;
     ulSize = secBufMsg[1].cbBuffer;
     pMessageFromServer = (PBYTE) calloc(ulSize, 1);
     if(!pMessageFromServer) {
          printf(
               "calloc Failed. GetLastError = 0x%08x\n",
               GetLastError());
          return 1;
     }
     ::CopyMemory(pMessageFromServer, secBufMsg[1].pvBuffer, ulSize);

     printf("Message from the server:\n");
     PrintHexDump(ulSize, pMessageFromServer);
     fwrite(pMessageFromServer, ulSize, 1, stdout);
     printf("\n");

     // 割り当てたメモリの解放
     if(pSignature) {
          free(pSignature); pSignature = NULL;
     }
     if(pMessage) {
          free(pMessage); pSignature = NULL;
     }
     if(pPadding) {
          free(pPadding); pPadding = NULL;
     }
     if(pMessageFromServer) {
          free(pMessageFromServer); pMessageFromServer = NULL;
     }


     //
     // SSPI のクリーンアップ
     //

     ss = DeleteSecurityContext(&hContext);

     if(SEC_E_OK != ss) {
          printf("DeleteSecurityContext Failed. ss = 0x%08x\n", ss);
          return 1;
     }

     ss = FreeCredentialsHandle(&hCredential);

     if(SEC_E_OK != ss) {
          printf("FreeCredentialsHandle Failed. ss = 0x%08x\n", ss);
          return 1;
     }


     //
     // 接続を閉じる
     //

     shutdown(hClientSocket, SD_SEND);
     shutdown(hClientSocket, SD_BOTH);
     closesocket(hClientSocket);

     WSACleanup ();

     return 0;
}

makefile は次の通り。

TARGETNAME=ntlmclt
OUTDIR=.\chk
LINK32=link.exe

ALL : "$(OUTDIR)\$(TARGETNAME).exe"

CPPFLAGS=\
	/nologo\
	/MT\
	/W4\
	/Fo"$(OUTDIR)\\"\
	/Fd"$(OUTDIR)\\"\
	/c\
	/Zi\
	/DWIN32\
	/DSECURITY_WIN32
		
LINK32_FLAGS=\
	Secur32.lib\
	Ws2_32.lib\
	/nologo\
	/subsystem:console\
	/pdb:"$(OUTDIR)\$(TARGETNAME).pdb"\
	/machine:I386\
	/out:"$(OUTDIR)\$(TARGETNAME).exe"\
	/DEBUG
	
LINK32_OBJS= \
	"$(OUTDIR)\$(TARGETNAME).obj"\
	"$(OUTDIR)\sockhelper.obj"

"$(OUTDIR)\$(TARGETNAME).exe" : "$(OUTDIR)" $(LINK32_OBJS)
    $(LINK32) $(LINK32_FLAGS) $(LINK32_OBJS)

"$(OUTDIR)" :
    @if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"

.cpp{$(OUTDIR)}.obj:
   $(CPP) $(CPPFLAGS) $<

上記の2ファイルに WinSock 用ヘルパー関数 を含め、 nmake すると ntlmclt.exe が出来上がります。

暗号化を行うときは InitializeSecurityContext に ASC_REQ_CONFIDENTIALITY フラグを渡すことに注意してください。

サーバー・プログラム

サーバー側コードは以下の通りです。

ntlmsrv.cpp として以下の内容を保存します。

#include <winsock2.h>
#include <security.h>
#include <sspi.h>
#include <stdio.h>
#include "sockhelper.h"


#define szMESSAGE_FROM_SERVER "このメッセージは ntlmsrv.exe が暗号化して送信しました."


int main(int argc, char* argv[]) {

     //
     // ネットワーク接続の準備
     //

     WSADATA wsaData;
     INT wRet = WSAStartup(WINSOCK_VERSION, &wsaData);
     if(wRet) {
          printf ("WSAStartup failed.\n");
          return 1;
     }
     
     
     INT nRet;

     // 待ち受け (LISTEN) ソケットの作成
     SOCKET hListenSocket = socket(AF_INET, SOCK_STREAM, 0);
     if(hListenSocket == INVALID_SOCKET) {
          printf(
               "socket Failed. "
               "WSAGetLastError = 0x%08x\n",
               WSAGetLastError());
          return 1;
     }
     ULONG ulAddress = inet_addr("127.0.0.1");
     SOCKADDR_IN name;
     name.sin_family = AF_INET;
     name.sin_port = htons(3000);
     memmove(&name.sin_addr, &ulAddress, sizeof(ULONG));

     // ソケットのバインド
     nRet = bind( hListenSocket, (SOCKADDR*)&name, sizeof(SOCKADDR_IN) );

     if(nRet) {
          printf(
               "bind Failed. "
               "WSAGetLastError = 0x%08x\n",
               WSAGetLastError());
          return 1;
     }

     // 待ち受け開始

     nRet = listen(hListenSocket, 2);

     if(nRet) {
          printf(
               "listen Failed. "
               "WSAGetLastError = 0x%08x\n",
               WSAGetLastError());
          return 1;
     }

     printf("Listening...\n");

     // クライアントの接続を待つ

     SOCKADDR_IN client_addr;
     INT addr_len = sizeof(client_addr);
     
     SOCKET hClientSocket = accept(
          hListenSocket, 
          (SOCKADDR*)&client_addr, 
          &addr_len);
     
     if(hClientSocket == INVALID_SOCKET) {
          printf(
               "accept Failed. "
               "WSAGetLastError = 0x%08x\n", 
               WSAGetLastError());
          return 1;
     }

     printf("Accepting a client.\n");

     closesocket(hListenSocket); // 待ち受け終了


     // SSPI (NTLM) クレデンシャルを取得
     
     CredHandle hCredential;
     TimeStamp tsExpiry;
     SECURITY_STATUS ss;

     ss = AcquireCredentialsHandle(
          NULL, 
          NTLMSP_NAME_A, //"NTLM", 
          SECPKG_CRED_BOTH,
          NULL, 
          NULL, 
          NULL, 
          NULL, 
          &hCredential, 
          &tsExpiry);

     if(ss != SEC_E_OK) {
          printf("AcquireCredentialsHandle Failed. ss = 0x%08x\n", ss);
          return 1;
     }

     //
     // クライアントとのハンドシェイク
     //

     SecBuffer secBufIn[1], secBufOut[1];
     SecBufferDesc secBufDescIn, secBufDescOut;
     BOOL bFirst = TRUE;
     CtxtHandle hContext;

     PBYTE pbBuff = NULL;
     ULONG ulSize = 0;

     ss = SEC_I_CONTINUE_NEEDED;

     while( ss == SEC_I_CONTINUE_NEEDED ) {

          // データサイズの取得
          ReceiveData( hClientSocket, (PBYTE)&ulSize, sizeof(ulSize) );

          if(ulSize < 1) {
               printf("Invalid Data - In SEC_I_CONTINUE_NEEDED loop\n");
               return 1;
          }

          // ulSize バイトのデータを受け取る
          pbBuff = (PBYTE) calloc(ulSize, 1);

          if( !pbBuff ) {
               printf(
                    "calloc Failed. "
                    "GetLastError = 0x%08x\n",
                    GetLastError());
               return 1;
          }

          ReceiveData( hClientSocket, pbBuff, ulSize );
          PrintHexDump( ulSize, pbBuff );

          secBufIn[0].BufferType = SECBUFFER_TOKEN;
          secBufIn[0].cbBuffer = ulSize;
          secBufIn[0].pvBuffer = pbBuff;
          secBufDescIn.cBuffers = 1;
          secBufDescIn.ulVersion = SECBUFFER_VERSION;
          secBufDescIn.pBuffers = secBufIn;

          secBufOut[0].BufferType = SECBUFFER_TOKEN;
          secBufOut[0].cbBuffer = 0;
          secBufOut[0].pvBuffer = NULL;
          secBufDescOut.cBuffers = 1;
          secBufDescOut.ulVersion = SECBUFFER_VERSION;
          secBufDescOut.pBuffers = secBufOut;

          ULONG ulAttr = 0;

          // セキュリティコンテキストの受け入れ
                    
          ss = AcceptSecurityContext(
               &hCredential, 
               bFirst ? NULL: &hContext,
               &secBufDescIn, 
               ulAttr | ASC_REQ_ALLOCATE_MEMORY | ASC_REQ_CONFIDENTIALITY,
               SECURITY_NETWORK_DREP,
               &hContext, 
               &secBufDescOut, 
               &ulAttr , 
               NULL);

          printf("AcceptSecurityContext - ss = 0x%08x\n", ss);

          bFirst = FALSE;

          if( secBufOut[0].cbBuffer ) {
               
               ULONG ulOutSize = secBufOut[0].cbBuffer;

               // データサイズを送信
               SendData( 
                    hClientSocket, 
                    (PBYTE) &ulOutSize, 
                    sizeof(ulOutSize) );
               
               // データの送信
               SendData(
                    hClientSocket, 
                    (PBYTE) secBufOut[0].pvBuffer, 
                    secBufOut[0].cbBuffer );
          
               FreeContextBuffer( secBufOut[0].pvBuffer );

          }

          if( pbBuff ) {
               free( pbBuff );
               pbBuff = NULL;
          }
          
     } // while SEC_I_CONTINUE_NEEDED


     //
     // クライアントの偽装
     //

     ss = ImpersonateSecurityContext( &hContext );

     if(SEC_E_OK != ss) {
          printf("ImpersonateSecurityContext Failed. - ss = 0x%08x\n", ss);
          return 1;
     }


     //
     // メッセージの暗号化と送信
     //

     SecPkgContext_Sizes PkgSize;

     ss = QueryContextAttributes( &hContext, SECPKG_ATTR_SIZES, &PkgSize );

     if(SEC_E_OK != ss) {
          printf("QueryContextAttributes Failed. - ss = 0x%08x\n", ss);
          return 1;
     }

     ULONG ulMsgSize = strlen(szMESSAGE_FROM_SERVER);
     PBYTE pSignature = (PBYTE) calloc(PkgSize.cbSecurityTrailer, 1);
     PBYTE pMessage = (PBYTE) calloc(ulMsgSize, 1);
     PBYTE pPadding = (PBYTE) calloc(PkgSize.cbBlockSize, 1);
     
     if(!pPadding || !pSignature || !pMessage) {
          printf(
               "calloc Failed. "
               "GetLastError = 0x%08x\n",
               GetLastError());
          return 1;
     }

     ::CopyMemory(pMessage, szMESSAGE_FROM_SERVER, ulMsgSize);

     SecBuffer secBufMsg[3];
     SecBufferDesc secBufDescMsg;

     // シグネチャバッファ
     secBufMsg[0].BufferType = SECBUFFER_TOKEN;
     secBufMsg[0].cbBuffer = PkgSize.cbSecurityTrailer;
     secBufMsg[0].pvBuffer = pSignature;
     
     // メッセージバッファ
     secBufMsg[1].BufferType = SECBUFFER_DATA;
     secBufMsg[1].cbBuffer = ulMsgSize;
     secBufMsg[1].pvBuffer = pMessage;

     // パディングバッファ
     secBufMsg[2].BufferType = SECBUFFER_PADDING;
     secBufMsg[2].cbBuffer = PkgSize.cbBlockSize;
     secBufMsg[2].pvBuffer = pPadding;

     // バッファ・ディスクリプション
     secBufDescMsg.cBuffers = 3;
     secBufDescMsg.ulVersion = SECBUFFER_VERSION;
     secBufDescMsg.pBuffers = secBufMsg;

     ss = EncryptMessage(
          &hContext, 
          0, 
          &secBufDescMsg, 
          0);

     if(SEC_E_OK != ss) {          
          printf("EncryptMessage Failed. - ss = 0x%08x\n", ss);
          return 1;
     }

     // トークンの送信
     ulSize = secBufMsg[0].cbBuffer;
     SendData(hClientSocket, (PBYTE)&ulSize, sizeof(ulSize));
     if(ulSize > 0) {
          SendData(hClientSocket, (PBYTE)secBufMsg[0].pvBuffer, ulSize);
     }

     printf("Token (%u Bytes):\n", ulSize);
     PrintHexDump(ulSize, (PBYTE)secBufMsg[0].pvBuffer);

     // メッセージの送信
     ulSize = secBufMsg[1].cbBuffer;
     SendData(hClientSocket, (PBYTE)&ulSize, sizeof(ulSize));
     if(ulSize > 0) {
          SendData(hClientSocket, (PBYTE)secBufMsg[1].pvBuffer, ulSize);
     }

     printf("Message (%u Bytes):\n", ulSize);
     PrintHexDump(ulSize, (PBYTE)secBufMsg[1].pvBuffer);

     // パディングの送信
     ulSize = secBufMsg[2].cbBuffer;
     SendData(hClientSocket, (PBYTE)&ulSize, sizeof(ulSize));
     if(ulSize > 0) {
          SendData(hClientSocket, (PBYTE)secBufMsg[2].pvBuffer, ulSize);
     }

     printf("Padding (%u Bytes):\n", ulSize);
     PrintHexDump(ulSize, (PBYTE)secBufMsg[2].pvBuffer);


     // 割り当てたメモリの解放
     if(pPadding) {
          free(pPadding); pPadding = NULL;
     }
     if(pSignature) {
          free(pSignature); pSignature = NULL;
     }
     if(pMessage) {
          free(pMessage); pMessage = NULL;
     }

     //
     // クライアント偽装の終了
     //

     RevertSecurityContext(&hContext);


     //
     // SSPI のクリーンアップ
     //

     ss = DeleteSecurityContext(&hContext);

     if(SEC_E_OK != ss) {
          printf("DeleteSecurityContext Failed. - ss = 0x%08x\n", ss);
          return 1;
     }

     ss = FreeCredentialsHandle(&hCredential);

     if(SEC_E_OK != ss) {
          printf("FreeCredentialsHandle Failed. - ss = 0x%08x\n", ss);
          return 1;
     }

     //
     // ネットワーク接続を閉じる
     //

     shutdown(hClientSocket, SD_SEND);
     shutdown(hClientSocket ,SD_BOTH);
     closesocket(hClientSocket);

     WSACleanup ();
     
     return 0;
}

makefile は次の通りです。

TARGETNAME=ntlmsrv
OUTDIR=.\chk
LINK32=link.exe

ALL : "$(OUTDIR)\$(TARGETNAME).exe"


CPPFLAGS=\
	/nologo\
	/MT\
	/W4\
	/Fo"$(OUTDIR)\\"\
	/Fd"$(OUTDIR)\\"\
	/c\
	/Zi\
	/DWIN32\
	/DSECURITY_WIN32
		
LINK32_FLAGS=\
	Secur32.lib\
	Ws2_32.lib\
	/nologo\
	/subsystem:console\
	/pdb:"$(OUTDIR)\$(TARGETNAME).pdb"\
	/machine:I386\
	/out:"$(OUTDIR)\$(TARGETNAME).exe"\
	/DEBUG
	
LINK32_OBJS= \
	"$(OUTDIR)\$(TARGETNAME).obj"\
	"$(OUTDIR)\sockhelper.obj"

"$(OUTDIR)\$(TARGETNAME).exe" : "$(OUTDIR)" $(LINK32_OBJS)
    $(LINK32) $(LINK32_FLAGS) $(LINK32_OBJS)

"$(OUTDIR)" :
    @if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)"

.cpp{$(OUTDIR)}.obj:
   $(CPP) $(CPPFLAGS) $<

ここまでお読みいただき、誠にありがとうございます。SNS 等でこの記事をシェアしていただけますと、大変励みになります。どうぞよろしくお願いします。

© 2024 Web/DB プログラミング徹底解説