JAVA处理SSL过程简单分析

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/MT4Develop/article/details/51308421

免责声明:本文仅内容作为学习交流使用,不可用于任何商业途径

遇到一个通过exe4j将java程序以及虚拟机环境都打包进来的一个exe桌面程序。需要了解其通讯内容,程序使用了SSL对通讯进行加密。

1.

首先直接用OD加载exe进程,然后对send下断点

6D6071E7    FF15 E4C0606D   call    dword ptr [<&WS2_32.#19>]        ; ws2_32.send
6D6071ED    85C0            test    eax, eax
6D6071EF    7E 07           jle     short 6D6071F8
6D6071F1    2BD8            sub     ebx, eax
6D6071F3    0145 F8         add     dword ptr [ebp-8], eax
6D6071F6    EB 33           jmp     short 6D60722B
6D6071F8    FF15 20C1606D   call    dword ptr [<&WS2_32.#111>]       ; ws2_32.WSAGetLastError
6D6071FE    3D 47270000     cmp     eax, 2747
6D607203    75 43           jnz     short 6D607248
6D607205    B8 00080000     mov     eax, 800
6D60720A    3BD8            cmp     ebx, eax
6D60720C    7E 09           jle     short 6D607217
6D60720E    8945 10         mov     dword ptr [ebp+10], eax
6D607211    8BF8            mov     edi, eax
6D607213    8BD8            mov     ebx, eax
6D607215  ^ EB C4           jmp     short 6D6071DB
6D607217    837D FC 1E      cmp     dword ptr [ebp-4], 1E
6D60721B    7D 24           jge     short 6D607241
6D60721D    68 E8030000     push    3E8
6D607222    FF15 34C0606D   call    dword ptr [<&KERNEL32.Sleep>]    ; kernel32.Sleep
6D607228    FF45 FC         inc     dword ptr [ebp-4]
6D60722B    85DB            test    ebx, ebx
6D60722D  ^ 7F AC           jg      short 6D6071DB
6D60722F    297D 1C         sub     dword ptr [ebp+1C], edi
6D607232    017D 18         add     dword ptr [ebp+18], edi
6D607235    837D 1C 00      cmp     dword ptr [ebp+1C], 0

此处的传给send方法的数据是已经进行过ssl加密的。此处代码处于net.dll(jre环境bin目录下的net.dll), 再根据调用栈观察调用方,发现是在jvm虚拟机中, 尝试跟虚拟机就与跟vmp壳的虚拟机类似,汇编代码一直处于解释代码的循环中,此处是解释java字节码,无法找到有用线索。

考虑一番后,决定先观察下java代码的情况.

2.

打开jar后,找到如下一段使用ssl的代码

       if (localObject1 != null) {
          Security.addProvider(new Provider());
          localObject2 = SSLContext.getInstance("SSLv3");
          KeyStore localKeyStore = KeyStore.getInstance("jks");
          char[] arrayOfChar = "123456".toCharArray();
          localKeyStore.load((InputStream)localObject1, arrayOfChar);
          KeyManagerFactory localKeyManagerFactory;
          (
            localKeyManagerFactory = KeyManagerFactory.getInstance("SunX509"))
            .init(localKeyStore, arrayOfChar);
          TrustManagerFactory localTrustManagerFactory;
          (
            localTrustManagerFactory = TrustManagerFactory.getInstance("SunX509"))
            .init(localKeyStore);

          ((SSLContext)localObject2).init(localKeyManagerFactory.getKeyManagers(), localTrustManagerFactory.getTrustManagers(), null);

          SSLSocket localSSLSocket = (SSLSocket)((SSLContext)localObject2).getSocketFactory().createSocket(this.c, this.d);
          this.b = localSSLSocket;

          this.b.setSoTimeout(this.f);

          ((InputStream)localObject1).close();
        }

明文写入SSLSocket提供的流,而SSLSocket由SSLConetext -> SSLSocketFactory -> 依次创建。

最初是在此行代码创建

localObject2 = SSLContext.getInstance("SSLv3");

首先观察SSLContext

import javax.net.ssl.SSLContext;

这个类在jre\lib\rt.jar中, 找到getInstance方法

  public static SSLContext getInstance(String paramString)
    throws NoSuchAlgorithmException
  {
       GetInstance.Instance localInstance = GetInstance.getInstance("SSLContext", SSLContextSpi.class, paramString);
       return new SSLContext((SSLContextSpi)localInstance.impl, localInstance.provider, paramString);
  }

继续找GetInstance类

import sun.security.jca.GetInstance;

此类也位于rt.jar中

  public static Provider.Service getService(String paramString1, String paramString2, String paramString3)
    throws NoSuchAlgorithmException, NoSuchProviderException
  {
        if ((paramString3 == null) || (paramString3.length() == 0))
            throw new IllegalArgumentException("missing provider");
        Provider localProvider = Providers.getProviderList().getProvider(paramString3);
        if (localProvider == null)
            throw new NoSuchProviderException("no such provider: " + paramString3);
        Provider.Service localService = localProvider.getService(paramString1, paramString2);
        if (localService == null)
            throw new NoSuchAlgorithmException("no such algorithm: " + paramString2 + " for provider " + paramString3);
        return localService;
  }

再找到Providers类

  public static ProviderList getProviderList()
  {
    ProviderList localProviderList = getThreadProviderList();
    if (localProviderList == null)
      localProviderList = getSystemProviderList();
    return localProviderList;
  }

  public static void setProviderList(ProviderList paramProviderList)
  {
    if (getThreadProviderList() == null)
      setSystemProviderList(paramProviderList);
    else
      changeThreadProviderList(paramProviderList);
  }

发现此处Provider只是保存了一个列表,此列表根据字符”SSLv3”来找到它所对应的提供服务的类。但此处无法知道具体是哪个类提供了此服务, 所以到此处也陷于了一点困境。

3.

思考之后,决定直接在所有openjdk代码中,找一下”SSLv3”这个字符。
发现在下面这些类中,还有这个字符

D:\openjdk\jdk\src\share\classes\com\sun\crypto\provider\TlsKeyMaterialGenerator.java                                
D:\openjdk\jdk\src\share\classes\sun\security\ssl\AppOutputStream.java                                               
D:\openjdk\jdk\src\share\classes\sun\security\ssl\CipherBox.java                                                     
D:\openjdk\jdk\src\share\classes\sun\security\ssl\CipherSuite.java                                                   
D:\openjdk\jdk\src\share\classes\sun\security\ssl\ClientHandshaker.java                                              
D:\openjdk\jdk\src\share\classes\sun\security\ssl\EngineInputRecord.java                                             
D:\openjdk\jdk\src\share\classes\sun\security\ssl\EngineOutputRecord.java                                            
D:\openjdk\jdk\src\share\classes\sun\security\ssl\HandshakeMessage.java                                              
D:\openjdk\jdk\src\share\classes\sun\security\ssl\InputRecord.java                                                   
D:\openjdk\jdk\src\share\classes\sun\security\ssl\ProtocolVersion.java                                               
D:\openjdk\jdk\src\share\classes\sun\security\ssl\Record.java                                                        
D:\openjdk\jdk\src\share\classes\sun\security\ssl\RSAClientKeyExchange.java                                          
D:\openjdk\jdk\src\share\classes\sun\security\ssl\ServerHandshaker.java                                              
D:\openjdk\jdk\src\share\classes\sun\security\ssl\SSLContextImpl.java                                                
D:\openjdk\jdk\src\share\classes\sun\security\ssl\SSLEngineImpl.java                                                 
D:\openjdk\jdk\src\share\classes\sun\security\ssl\SSLSessionImpl.java                                                
D:\openjdk\jdk\src\share\classes\sun\security\ssl\SSLSocketImpl.java                                                 
D:\openjdk\jdk\src\share\classes\sun\security\ssl\SunJSSE.java                                                       
D:\openjdk\jdk\test\sun\security\pkcs11\fips\CipherTest.java                                                         
D:\openjdk\jdk\test\sun\security\pkcs11\sslecc\CipherTest.java                                                       
D:\openjdk\jdk\test\sun\security\ssl\com\sun\net\ssl\internal\ssl\GenSSLConfigs\Traffic.java                         
D:\openjdk\jdk\test\sun\security\ssl\com\sun\net\ssl\internal\ssl\ProtocolVersion\HttpsProtocols.java                
D:\openjdk\jdk\test\sun\security\ssl\com\sun\net\ssl\internal\ssl\SSLEngineImpl\DelegatedTaskWrongException.java     
D:\openjdk\jdk\test\sun\security\ssl\com\sun\net\ssl\internal\ssl\SSLEngineImpl\SSLEngineBadBufferArrayAccess.java   
D:\openjdk\jdk\test\sun\security\ssl\com\sun\net\ssl\internal\ssl\SSLSocketImpl\SetClientMode.java                   
D:\openjdk\jdk\test\sun\security\ssl\javax\net\ssl\SSLContextVersion.java                                            
D:\openjdk\jdk\test\sun\security\ssl\javax\net\ssl\FixingJavadocs\SSLSocketInherit.java                              
D:\openjdk\jdk\test\sun\security\ssl\javax\net\ssl\NewAPIs\testEnabledProtocols.java                                 
D:\openjdk\jdk\test\sun\security\ssl\javax\net\ssl\NewAPIs\SSLEngine\TestAllSuites.java                              
D:\openjdk\jdk\test\sun\security\ssl\javax\net\ssl\TLSv12\DisabledShortRSAKeys.java                                  
D:\openjdk\jdk\test\sun\security\ssl\sanity\ciphersuites\CipherSuitesInOrder.java                                    
D:\openjdk\jdk\test\sun\security\ssl\sanity\interop\CipherTest.java                                               

通过排查,认为\jdk\src\share\classes\sun\security\ssl目录下相关的类应该就是要找的类。
最后确定是SSLSocketImpl这个类处理了明文与密文之间的转换

    private void readRecord(InputRecord r, boolean needAppData)                                  
            throws IOException {                                                                 
        int state;                                                                               

        // readLock protects reading and processing of an InputRecord.                           
        // It keeps the reading from sockInput and processing of the record                      
        // atomic so that no two threads can be blocked on the                                   
        // read from the same input stream at the same time.                                     
        // This is required for example when a reader thread is                                  
        // blocked on the read and another thread is trying to                                   
        // close the socket. For a non-autoclose, layered socket,                                
        // the thread performing the close needs to read the close_notify.                       
        //                                                                                       
        // Use readLock instead of 'this' for locking because                                    
        // 'this' also protects data accessed during writing.                                    
      synchronized (readLock) {                                                                  
        /*                                                                                       
         * Read and handle records ... return application data                                   
         * ONLY if it's needed.                                                                  
         */                                                                                      

        while (((state = getConnectionState()) != cs_CLOSED) &&                                  
                (state != cs_ERROR) && (state != cs_APP_CLOSED)) {                               
            /*                                                                                   
             * Read a record ... maybe emitting an alert if we get a                             
             * comprehensible but unsupported "hello" message during                             
             * format checking (e.g. V2).                                                        
             */                                                                                  
            try {                                                                                
                r.setAppDataValid(false);                                                        
                r.read(sockInput, sockOutput);                                                   
            } catch (SSLProtocolException e) {                                                   
                try {                                                                            
                    fatal(Alerts.alert_unexpected_message, e);                                   
                } catch (IOException x) {                                                        
                    // discard this exception                                                    
                }                                                                                
                throw e;                                                                         
            } catch (EOFException eof) {                                                         
                boolean handshaking = (getConnectionState() <= cs_HANDSHAKE);                    
                boolean rethrow = requireCloseNotify || handshaking;                             
                if ((debug != null) && Debug.isOn("ssl")) {                                      
                    System.out.println(threadName() +                                            
                        ", received EOFException: "                                              
                        + (rethrow ? "error" : "ignored"));                                      
                }                                                                                
                if (rethrow) {                                                                   
                    SSLException e;                                                              
                    if (handshaking) {                                                           
                        e = new SSLHandshakeException                                            
                            ("Remote host closed connection during handshake");                  
                    } else {                                                                     
                        e = new SSLProtocolException                                             
                            ("Remote host closed connection incorrectly");                       
                    }                                                                            
                    e.initCause(eof);                                                            
                    throw e;                                                                     
                } else {                                                                         
                    // treat as if we had received a close_notify                                
                    closeInternal(false);                                                        
                    continue;                                                                    
                }                                                                                
            }                                                                                    


            /*                                                                                   
             * The basic SSLv3 record protection involves (optional)                             
             * encryption for privacy, and an integrity check ensuring                           
             * data origin authentication.  We do them both here, and                            
             * throw a fatal alert if the integrity check fails.                                 
             */                                                                                  
            try {                                                                                
                r.decrypt(readMAC, readCipher);                                                  
            } catch (BadPaddingException e) {                                                    
                byte alertType = (r.contentType() == Record.ct_handshake)                        
                                        ? Alerts.alert_handshake_failure                         
                                        : Alerts.alert_bad_record_mac;                           
                fatal(alertType, e.getMessage(), e);                                             
            }                                                                                    

           //此处添加日志代码

            // if (!r.decompress(c))                                                             
            //     fatal(Alerts.alert_decompression_failure,                                     
            //         "decompression failure");                                                 

            /*                                                                                   
             * Process the record.                                                               
             */                                                                                  
            synchronized (this) {                                                                
              switch (r.contentType()) {                                                         
                case Record.ct_handshake:                                                        
                    /*                                                                           
                     * Handshake messages always go to a pending session                         
                     * handshaker ... if there isn't one, create one.  This                      
                     * must work asynchronously, for renegotiation.                              
                     *                                                                           
                     * NOTE that handshaking will either resume a session                        
                     * which was in the cache (and which might have other                        
                     * connections in it already), or else will start a new                      
                     * session (new keys exchanged) with just this connection                    
                     * in it.                                                                    
                     */                                                                          
                    initHandshaker();                                                            
                    if (!handshaker.activated()) {                                               
                        // prior to handshaking, activate the handshake                          
                        if (connectionState == cs_RENEGOTIATE) {                                 
                            // don't use SSLv2Hello when renegotiating                           
                            handshaker.activate(protocolVersion);                                
                        } else {                                                                 
                            handshaker.activate(null);                                           
                        }                                                                        
                    }                                                                            

                    /*                                                                           
                     * process the handshake record ... may contain just                         
                     * a partial handshake message or multiple messages.                         
                     *                                                                           
                     * The handshaker state machine will ensure that it's                        
                     * a finished message.                                                       
                     */                                                                          
                    handshaker.process_record(r, expectingFinished);                             
                    expectingFinished = false;                                                   

                    if (handshaker.invalidated) {                                                
                        handshaker = null;                                                       
                        // if state is cs_RENEGOTIATE, revert it to cs_DATA                      
                        if (connectionState == cs_RENEGOTIATE) {                                 
                            connectionState = cs_DATA;                                           
                        }                                                                        
                    } else if (handshaker.isDone()) {                                            
                        // reset the parameters for secure renegotiation.                        
                        secureRenegotiation =                                                    
                                        handshaker.isSecureRenegotiation();                      
                        clientVerifyData = handshaker.getClientVerifyData();                     
                        serverVerifyData = handshaker.getServerVerifyData();                     

                        sess = handshaker.getSession();                                          
                        handshakeSession = null;                                                 
                        handshaker = null;                                                       
                        connectionState = cs_DATA;                                               

                        //                                                                       
                        // Tell folk about handshake completion, but do                          
                        // it in a separate thread.                                              
                        //                                                                       
                        if (handshakeListeners != null) {                                        
                            HandshakeCompletedEvent event =                                      
                                new HandshakeCompletedEvent(this, sess);                         

                            Thread t = new NotifyHandshakeThread(                                
                                handshakeListeners.entrySet(), event);                           
                            t.start();                                                           
                        }                                                                        
                    }                                                                            

                    if (needAppData || connectionState != cs_DATA) {                             
                        continue;                                                                
                    }                                                                            
                    break;                                                                       

                case Record.ct_application_data:                                                 
                    // Pass this right back up to the application.                               
                    if (connectionState != cs_DATA                                               
                            && connectionState != cs_RENEGOTIATE                                 
                            && connectionState != cs_SENT_CLOSE) {                               
                        throw new SSLProtocolException(                                          
                            "Data received in non-data state: " +                                
                            connectionState);                                                    
                    }                                                                            
                    if (expectingFinished) {                                                     
                        throw new SSLProtocolException                                           
                                ("Expecting finished message, received data");                   
                    }                                                                            
                    if (!needAppData) {                                                          
                        throw new SSLException("Discarding app data");                           
                    }                                                                            

                    r.setAppDataValid(true);                                                     
                    break;                                                                       

                case Record.ct_alert:                                                            
                    recvAlert(r);                                                                
                    continue;                                                                    

                case Record.ct_change_cipher_spec:                                               
                    if ((connectionState != cs_HANDSHAKE                                         
                                && connectionState != cs_RENEGOTIATE)                            
                            || r.available() != 1                                                
                            || r.read() != 1) {                                                  
                        fatal(Alerts.alert_unexpected_message,                                   
                            "illegal change cipher spec msg, state = "                           
                            + connectionState);                                                  
                    }                                                                            

                    //                                                                           
                    // The first message after a change_cipher_spec                              
                    // record MUST be a "Finished" handshake record,                             
                    // else it's a protocol violation.  We force this                            
                    // to be checked by a minor tweak to the state                               
                    // machine.                                                                  
                    //                                                                           
                    changeReadCiphers();                                                         
                    // next message MUST be a finished message                                   
                    expectingFinished = true;                                                    
                    continue;                                                                    

                default:                                                                         
                    //                                                                           
                    // TLS requires that unrecognized records be ignored.                        
                    //                                                                           
                    if (debug != null && Debug.isOn("ssl")) {                                    
                        System.out.println(threadName() +                                        
                            ", Received record type: "                                           
                            + r.contentType());                                                  
                    }                                                                            
                    continue;                                                                    
              } // switch                                                                        

              /*                                                                                 
               * Check the sequence number state                                                 
               *                                                                                 
               * Note that in order to maintain the connection I/O                               
               * properly, we check the sequence number after the last                           
               * record reading process. As we request renegotiation                             
               * or close the connection for wrapped sequence number                             
               * when there is enough sequence number space left to                              
               * handle a few more records, so the sequence number                               
               * of the last record cannot be wrapped.                                           
               */                                                                                
              if (connectionState < cs_ERROR) {                                                  
                  checkSequenceNumber(readMAC, r.contentType());                                 
              }                                                                                  

              return;                                                                            
            } // synchronized (this)                                                             
        }                                                                                        

        //                                                                                       
        // couldn't read, due to some kind of error                                              
        //                                                                                       
        r.close();                                                                               
        return;                                                                                  
      }  // synchronized (readLock)                                                              
    }                                                                                            

密文数据在此处解密

r.decrypt(readMAC, readCipher);  

在此方法后添加自己的代码,将r这个流中内容输出到日志,然后手动重新编译这些相关的类,并覆盖到java环境的lib目录中,程序运行并在此处输出了明文日志。

猜你喜欢

转载自blog.csdn.net/MT4Develop/article/details/51308421