Assemblerで最も単純なSOCKS4サーバーを作成する

しばらく前、私は自分のニーズに合わせてプロキシサーバーを実装し、将来的に使用できるようにしたり、サイズを最小化したいと考えていました。 私にとって自然なオプションは、アセンブラーを使用した実装でした。 プログラムは小さく、便利であることが判明し、将来的には頻繁に使用しました。 そして今、長年にわたって、1つのプロトコルSOCKS4の最も単純な実装を示したいと思います。 このプロトコルは、ファイアウォールの背後のローカルネットワークにあるクライアントが外部ネットワークにアクセスできるように作成されました。 同時に、この場合、顧客の要求を制御することができます:)実装するときに最初に必要なことは、このプロトコルを説明するドキュメントを読むことです。 したがって、ドキュメント:



SOCKSプロトコルの説明

SOCKS:ファイアウォールを越えたTCPプロキシのプロトコル



次に、説明を準備して、先に進みましょう。 プロキシサーバーの機能は、特定の形式でクライアントからの要求を受け入れ、ソケットを形成し、それをクライアントが要求するアドレスに接続し、サーバーまたはクライアントによって閉じられる前に2つのソケット間でデータの交換を保証することです。 実装に取り​​かかりましょう。







プログラムで使用されるマクロとデータ構造



インクルードファイル、includes.incを作成します。 Windowsプログラムを作成するときに、SOCKS4を操作するための標準マクロ+構造を配置します。 ここでは、すべてのマクロを提供するのではなく、主な問題を解決するために必要な説明と機能のみを提供します。ソースコードを含む添付ファイルには他のすべてが含まれています。

; SOCKS4 – ,  ,    ;   (DSTIP)/(DSTPORT) CONNECT_SOCK4 Struc VN Db ? CD Db ? DSTPORT Dw ? DSTIP Dd ? NULL Db ? CONNECT_SOCK4 Ends ; SOCKS4 -  -  . RESPONSE_SOCK4 Struc VN Db ? CD Db ? DSTPORT Dw ? DSTIP Dd ? RESPONSE_SOCK4 Ends
      
      







概して、CONNECT_SOCK4構造体とRESPONSE_SOCK4構造体は、承認なしでプロトコルを実装するため、違いはありません。 しかし、将来は修正のためにそれらを簡単に変更できるように、それらをすべて同じままにすることにしました。 構造自体では、VN変数で-プロトコルのバージョンが示されます。この場合、常に4が必要です。SOCKS5の場合、この変数には5が含まれます(プロトコルは基本的に同様です)。 CD変数は、クライアントが要求したアドレスへのプロキシサーバー要求の結果をクライアントに返すために使用されます(90-接続成功/ 91-接続失敗)。

実際、プログラムには3つの段階があります。

*最初に、ソケットを初期化し、クライアント要求のソケットをリッスンし、処理フローを作成します。

* 2番目の段階は、クライアントのリクエストの分析、クライアントがリクエストしたサーバーへのソケットの作成と接続の試行です。

*最後の3番目の段階は、クライアントソケットと、要求されたアドレスで作成および接続されたソケットとの間のデータ転送です。



プログラムの初期化、第1段階の実装:



 ;  ,     WinMain Proc LOCAL ThreadId, hServSock :DWORD LOCAL hostname[256] :BYTE LOCAL _wsa :WSADATA LOCAL _our :sockaddr_in ;     ,     1.1, ;     invoke WSAStartup, 0101h, ADDR _wsa .if eax == 0 ;   ,  ,     invoke gethostname, ADDR hostname, 256 invoke gethostbyname, ADDR hostname .if eax == 0 invoke inet_addr, ADDR hostname .else mov eax, [eax + 12] mov eax, [eax] mov eax, [eax] .endif mov _our.sin_addr, eax invoke inet_ntoa, eax mov _our.sin_family, AF_INET mov _our.sin_addr.S_un.S_addr, INADDR_ANY xor eax, eax ;  ,       mov ax, SOCKS_PORT invoke htons, eax mov _our.sin_port, ax invoke socket, AF_INET, SOCK_STREAM, 0 .if eax != INVALID_SOCKET ;     mov hServSock, eax ;          invoke bind, hServSock, ADDR _our, SIZEOF sockaddr_in .if eax != SOCKET_ERROR @@: ;     invoke listen, hServSock, SOMAXCONN .repeat ;  ,      invoke accept, hServSock, NULL, NULL .until eax != INVALID_SOCKET ;  ,       xchg eax, ebx invoke CreateThread, NULL, NULL, ADDR socketThread, ebx, NULL, ADDR ThreadId ;     jmp @B .endif .endif invoke closesocket, hServSock .endif invoke ExitProcess, 0 WinMain Endp
      
      





これが最初の手順です。理解できるようにコードにできる限りコメントを付けましたが、まだ不明な点がある場合は、私またはMSDNに連絡してください。 原則として、すべてのコードはMASMおよびWinAPI構文を使用して記述されています。 上記の関数の結果は、マシンのネットワークアドレスの1つ(ローカルアドレス、または実際のIPがある場合は外部アドレス)で動作するソケットになります+クライアントを接続することにより、関数は着信クライアントで動作するために使用される別のストリームを作成します。 それでは先に進みましょう...



第二段階、顧客要求分析



2番目の段階で行う必要があるのは、CONNECT_SOCK4構造体を受け入れ、ソケットを作成し、接続を試みて、クライアントに応答を送信することだけです。 実装:



 socketThread Proc sock:DWORD LOCAL lpMem, _csock, ThreadId, dAmount :DWORD LOCAL Remote :sockaddr_in LOCAL wrFds, rdFds :fd_set LOCAL hResp :RESPONSE_SOCK4 ;       invoke FdZero, ADDR rdFds invoke FdSet, sock, ADDR rdFds invoke select, NULL, ADDR rdFds, NULL, NULL, NULL ;      invoke ioctlsocket, sock, FIONREAD, ADDR dAmount ;     mov lpMem, @Result(LocalAlloc, LMEM_FIXED or LMEM_ZEROINIT, dAmount) ;      invoke recv, sock, lpMem, dAmount, 0 ;   lea edi, hResp mov esi, lpMem ;  Esi   .   ()   SOCKS4, ; SOCKS5      ,    ... Assume Esi : Ptr CONNECT_SOCK4 Assume Edi : Ptr RESPONSE_SOCK4 .if [esi].VN == 4 ;    4 .if [esi].CD == 1 invoke socket, AF_INET, SOCK_STREAM, 0 .if eax != INVALID_SOCKET mov _csock, eax ;    ,      mov Remote.sin_family, AF_INET mov ax, [esi].DSTPORT mov Remote.sin_port, ax mov eax, [esi].DSTIP mov Remote.sin_addr, eax mov cx, [esi].DSTPORT mov edx, [esi].DSTIP ;  Edi    mov [edi].VN, 0 mov [edi].DSTPORT, cx mov [edi].DSTIP, edx ;      invoke connect, _csock, ADDR Remote, SIZEOF Remote .if !eax ;  ,    mov [edi].CD, 90 ;   ,     invoke send, sock, ADDR hResp, SIZEOF RESPONSE_SOCK4, 0 ;        ;    ; -        , ;   ; -       , ;     mov ebx, @Result(LocalAlloc, LMEM_FIXED or LMEM_ZEROINIT, SIZEOF THREAD_DATA) Assume Ebx : Ptr THREAD_DATA mov eax, _csock mov [ebx].Server, eax mov eax, sock mov [ebx].Client, eax Assume Ebx : Nothing ;     (    ;    ) invoke CreateThread, NULL, NULL, ADDR ClientSock, ebx, NULL, ADDR ThreadId .else ;     -    invoke closesocket, _csock ; ,     mov [edi][RESPONSE_SOCK4.CD], 91 ;   ,     invoke send, sock, ADDR hResp, SIZEOF RESPONSE_SOCK4, 0 .endif .endif .endif .endif Assume Edi: Nothing Assume Esi: Nothing ;  ,    invoke LocalFree, lpMem ret socketThread Endp
      
      





このプロシージャの結果は、接続されたソケットと、2つのソケット間のデータ交換を実装する作成されたストリームです。 すべてがシンプルです。 1つだけ明確にする必要があります。MASMで導入された構造内のアドレス指定にいくつかのポイントを使用して、プログラマの生活を楽にします。 最初の瞬間、「想定」マクロ。

Esume:Ptr CONNECT_SOCK4は、CONNECT_SOCK4構造体のアドレスがこのレジスター(Esi)にあることをコンパイラーに伝えます。これにより、この構造体内の変数へのアクセスがさらに簡単になります。 Esiを想定:バインディングを元に戻すものは何もありません。 理解を深めるために、複数のアドレスオプションを指定すると簡単になります。

 Assume Esi:Ptr CONNECT_SOCK4 mov al, [esi].VN ;   AL     VN  mov al, [esi].CD ;   AL  CD mov ax. [esi].DSTPORT ;   AX  DSTPORT Assume Esi:Nothing
      
      





どちらか

 mov al, [esi][CONNET_SOCK4.VN] ;   AL     VN mov al, [esi][CONNET_SOCK4.CD] ;   AL  CD mov ax, [esi][CONNET_SOCK4.DSTPORT] ;   AX  DSTPORT
      
      





どちらか

 mov al, byte ptr [esi] ;   AL  VN mov al, byte ptr [esi+1] ;   AL  CD mov ax, word ptr [esi+2] ;   AX  DSTPORT
      
      







最初のオプションを使用する方がより速く、より便利で、より視覚的であることはあなたにも私にも明らかだと思います。 ただし、構造体の1つの変数を参照する必要がある場合、2番目のオプションには存在する権利があります。 住所のデータが構造化されていない場合は、3番目のオプションを使用することをお勧めします。 しかし、ご存知のように、味と色はそれぞれタンボフのオオカミです。 より便利な方法を使用してください。

明確にする価値があるもう1つのポイント。 マクロ結果 。 このマクロは、WinAPI関数を1行で呼び出して、実行結果をレジスタまたはメモリに格納できるように作成されています。 だから行:

 mov lpMem, @Result(LocalAlloc, LMEM_FIXED or LMEM_ZEROINIT, dAmount)
      
      





この種の呼び出しを最初に実行します。

 invoke LocalAlloc, LMEM_FIXED or LMEM_ZEROINIT, dAmount
      
      





この呼び出しを行った後、実行結果(Eax)がlpMem変数に入力されます。 この特定のケースでは、メモリが割り当てられ、割り当てられた領域が配置されているアドレスが変数に書き込まれます。



ステージ3、データ転送



したがって、2つの最も困難な段階が完了しました。 クライアントが来て、私たちはそれをリモートサーバーに接続し、それは最も単純な「猿」の仕事の番でした。 2つのソケット間のデータ転送。 簡単かつ迅速にやってみましょう:

 ;         .... ClientSock Proc Param:DWORD LOCAL sserver, sclient:DWORD LOCAL rdFds :fd_set LOCAL dAmount, lpBuf: DWORD ;  Param         , ;     mov ebx, Param Assume Ebx: Ptr THREAD_DATA mov eax, [ebx].Server mov sserver, eax mov eax, [ebx].Client mov sclient, eax Assume Ebx : Nothing ;     invoke LocalFree, Param @@: invoke FdZero, ADDR rdFds invoke FdSet, sserver, ADDR rdFds invoke FdSet, sclient, ADDR rdFds invoke select, NULL, ADDR rdFds, NULL, NULL, NULL ; ,      .if eax == SOCKET_ERROR || eax == 0 ;   -  jmp @F .endif ;     ,    ? invoke FdIsSet, sserver, ADDR rdFds .if eax ;      invoke ioctlsocket, sserver, FIONREAD, ADDR dAmount ;     mov lpBuf, @Result(LocalAlloc, LMEM_FIXED or LMEM_ZEROINIT, dAmount) invoke recv, sserver, lpBuf, dAmount, 0 .if eax == SOCKET_ERROR || eax == 0 jmp @F .endif invoke send, sclient, lpBuf, eax, 0 invoke LocalFree, lpBuf .endif ;         ? invoke FdIsSet, sclient, ADDR rdFds .if eax ;      invoke ioctlsocket, sclient, FIONREAD, ADDR dAmount ;     mov lpBuf, @Result(LocalAlloc, LMEM_FIXED or LMEM_ZEROINIT, dAmount) invoke recv, sclient, lpBuf, dAmount, 0 .if eax == SOCKET_ERROR || eax == 0 jmp @F .endif invoke send, sserver, lpBuf, eax, 0 invoke LocalFree, lpBuf .endif ;     jmp @B @@: ;   invoke closesocket, sserver invoke closesocket, sclient ;    invoke ExitThread, 0 ClientSock Endp
      
      





最初に、このプロシージャは内部変数をストリームに渡された構造体から初期化して、使用しやすくします。 次に、ループ内で、ソケットから読み取るデータがあるかどうかを確認し、次に2つのコード(実際にはコピーアンドペースト、ここでは関数と最適化の削除はわかりやすいので気にしませんでした)は1つのソケットから読み取り、2番目のソケットに送信します。

すべて、乾杯! コンパイルして試してみます。 原則として、最適なオプションはFireFoxです。 接続設定で、SOCKS4プロキシサーバーを使用する必要があることを示します。 彼の住所とポートを示します。 その後、設定を保存してインターネットを利用し、プロキシを通過します(3.5 kb)))はい、わかります。 コンパイルには、 MASM32パッケージがインストールされていることが望ましく、コンパイルはbldall.batまたはbldallc.batバッチファイル(アプリケーションのコンソールバージョン)によって実行されます。



アプリケーションのソースコードといくつかの古いアセンブラプロジェクトは、次の場所にあります。



ソックス4



All Articles