sql server 經過sql server 協議進行登陸的解析

  爲了在oneproxy-monitor中實現sql server的先後端登陸分離,通過一段時間的研究終於把sql server的登陸搞定了。目前能夠作到前端經過一個密碼鏈接到中間件oneproxy-for-sqlserver(oneproxy-monitor的sqlserver版本)。oneproxy-for-sqlserver再經過數據庫密碼登陸到後端sql server數據庫,再實現先後端數據的直接轉發功能。前端

    sql server登陸過程的數據包交互狀況以下圖所示,登陸過程當中從大致上來講指涉及到兩個請求包和兩個響應數據包。其中prelogin數據包是爲了本次鏈接把客戶端的環境信息發送到服務端,這個數據包中包括:客戶端的版本信息,是否要求加密等8類數據而且以0xff來結束,在0xff結束後在放置前面各類類型的具體數據內容。能夠參見MSDN。prelogin的響應包是對應類型的響應狀況。這一步比較簡單,能夠直接進行解包和回覆響應包。git

    目前我研究的對象是sql server 2016,從sql server的官方文檔能夠知道,不論是否配置使用ssl仍是不使用ssl,在登陸的過程當中都會 使用ssl來保護login7的數據包,防止中間人攻擊。在login7中包含了用戶的用戶名,密碼,以及前端應用信息,tds版本,客戶端可以接收的包大小,客戶端進程ID, 鏈接ID等。可見這個數據包是登陸過程當中關鍵的數據包。只要搞定這個數據包登陸過程就可以搞定了。github

   要解析出login7包中的內容,就須要在中間件中實現ssl的服務端環境。經過ssl來對數據進行解密和加密login7數據包。要實現ssl端的服務端環境,則須要解決兩個問題:sql

1. ssl服務端的證書問題數據庫

2. 解決ssl的加密數據包中增長和去掉sqlserver的包頭的問題後端

解決ssl服務端的證書問題

    開始覺得sqlserver會在主機上面生成證書,而且保存到指定的文件中。因而在主機上面搜索sql server的證書,結果...,而後沒有結果了。因而閱讀msdn上面的文章,才瞭解到sql server當用戶設置不使用ssl加密時,會生成自簽名證書。可是沒有說到是否生成證書文件,在stackoverflow上面瞭解到是沒有生成證書文件,只是在sql server啓動時內存中生成證書信息,把證書信息保存在內存中的。api

    因而經過閱讀openssl的源代碼瞭解內存中證書的使用方法,以及使用openssl api生成證書的實現方式。其中openssl api生成證書的實現能夠參見開源軟件ssl-cert-generator-lib。在經過以下兩個函數把證書和私鑰載入到ssl上下文中。session

int SSL_CTX_use_PrivateKey(SSL_CTX *ctx, EVP_PKEY *pkey);
int SSL_CTX_use_certificate(SSL_CTX *ctx, X509 *x);

須要注意的是不能直接使用ssl-cert-generator-lib中返回的public_key_pem和private_key_pem來設置,不然會獲得wrong tag的錯誤。函數

解決ssl加密包頭的問題

    經過閱讀msdn的文檔能夠知道,並非把login7數據包直接經過ssl來發送便可,還須要在加密後的數據包中增長一個sql server包的頭信息。經過閱讀openssl發數據的代碼(以下所示),能夠知道在發送的數據會先調用回調函數,只要設置這個回調函數就能夠在發送加密數據包以前增長sql server包的頭信息。sqlserver

int BIO_read(BIO *b, void *out, int outl)
{
    int i;
    long (*cb) (BIO *, int, const char *, int, long, long);

    if ((b == NULL) || (b->method == NULL) || (b->method->bread == NULL)) {
        BIOerr(BIO_F_BIO_READ, BIO_R_UNSUPPORTED_METHOD);
        return (-2);
    }

    cb = b->callback;
    if ((cb != NULL) &&
        ((i = (int)cb(b, BIO_CB_READ, out, outl, 0L, 1L)) <= 0))
        return (i);

    if (!b->init) {
        BIOerr(BIO_F_BIO_READ, BIO_R_UNINITIALIZED);
        return (-2);
    }

    i = b->method->bread(b, out, outl);

    if (i > 0)
        b->num_read += (unsigned long)i;

    if (cb != NULL)
        i = (int)cb(b, BIO_CB_READ | BIO_CB_RETURN, out, outl, 0L, (long)i);
    return (i);
}

從上面代碼中可以瞭解到只要實現BIO結構中的callback便可。而這個callback正是SSL結構中的rbio指針的callback。而且openssl提供了以下的函數來設置這個回調函數:

void BIO_set_callback(BIO *b,
                      long (*callback) (struct bio_st *, int, const char *,
                                        int, long, long));

如今主要來說解回調函數的定義狀況,下面是oneproxy-monitor中針對sqlserver的回調函數指針的定義。

typedef long (*handle_data_callback) (struct bio_st *bio, int oper, const char *data, int dataLen, long argl, long ret);

須要注意的是oper的值,其中oper的能夠取值:BIO_CB_WRITE,BIO_CB_READ,BIO_CB_RETURN等,當發送數據的時候oper將是BIO_CB_WRITE,發送完畢後oper的值爲BIO_CB_WRITE|BIO_CB_RETURN.當開始讀取數據時oper是BIO_CB_READ,讀取完畢後BIO_CB_RETURN.

下面來講一個有趣的事情(見下面ssl結構的部分代碼):先說明我使用的是openssl-1.0.2e版本,從這個結構中能夠了解到ssl結構中提供了BIO *rbio, BIO* wbio以及BIO* bbio.我開始覺得openssl是經過bbio來進行讀寫數據的,因而經過BIO_set_callback來設置bbio的回調函數,結果直接core了。經過閱讀代碼才知道原來這個啃爹的盡然爲NULL,並無設置。這個讓我很不能理解openssl爲啥這麼幹,這不是明顯的站着資源不幹活嘛。

struct ssl_st {
    /*
     * protocol version (one of SSL2_VERSION, SSL3_VERSION, TLS1_VERSION,
     * DTLS1_VERSION)
     */
    int version;
    /* SSL_ST_CONNECT or SSL_ST_ACCEPT */
    int type;
    /* SSLv3 */
    const SSL_METHOD *method;
    /*
     * There are 2 BIO's even though they are normally both the same.  This
     * is so data can be read and written to different handlers
     */
#  ifndef OPENSSL_NO_BIO
    /* used by SSL_read */
    BIO *rbio;
    /* used by SSL_write */
    BIO *wbio;
    /* used during session-id reuse to concatenate messages */
    BIO *bbio;
#  else
    /* used by SSL_read */
    char *rbio;
    /* used by SSL_write */
    char *wbio;
    char *bbio;
#  endif

第二個有趣的事情是:因爲sql server在讀和寫的時候都須要對包頭進行操做。讀的時候須要去掉sql server的額外包頭,在寫的時候要增長額外包頭。因而看了BIO_set_callback的函數後秒懂,不就是設置兩個回調函數嘛?因而匆匆寫了一個寫的回調函數,一個讀的回調函數,而且經過BIO_set_callback函數設置到對應的BIO上面,結果測試發現先設置的回調函數老是失效。這個坑爹的,連個回調函數都設置不成功了。把本身的代碼閱讀了n遍發現沒有錯誤呀,最後再次閱讀openssl的代碼,發現rbio和wbio竟然是同一個結構。在經過BIO_set_callback設置rbio的回調函數的同時也就是把wbio的回調函數給設置了,反之也是。這個地方又體現了openssl坑爹的一面。竟然設置wbio的回調函數把rbio的回調函數也設置了。這個怎麼看怎麼怪。爲啥不把bbio也指向rbio和wbio的結構 呢,這樣經過設置bbio的回調函數來設置讀和寫的回調函數不是更加合理麼?

更多信息,請關注oneproxy-monitor

相關文章
相關標籤/搜索