當前位置:
首頁 > 新聞 > Hooking Chrome瀏覽器的SSL函數來讀取SSL通信數據

Hooking Chrome瀏覽器的SSL函數來讀取SSL通信數據

2015年,NetRipper首次在Defcon大會上面世。NetRipper是一款針對Windows操作系統的漏洞利用工具,它可以使用API hooking從一個低許可權的用戶那裡截獲網路通信數據以及與加密相關的信息,而且還可以捕獲明文通信數據以及經過加密的通信數據。這是NetRipper在github上的詳細描述,另外,NetRipper還提供了metasploit和powershell版本的利用模塊。

何為NetRipper?

在NetRipper剛出現的時候,就有研究人員注意到NetRipper還可以對火狐瀏覽器,Chrome瀏覽器,Lync(Skype的一項業務),puTTY,WinSCP,SQL伺服器管理程序以及微軟Outlook客戶端進行數據注入以及捕獲相關的網路數據。

NetRipper主要是通過Hook進程的網路函數關鍵點(封包加密之前與封包解密之後的網路函數)來劫持客戶端程序的明文數據。其中包括了許多主流客戶端,例如:Chrome,Firefox,IE,WinSCP,Putty以及一些代碼庫中提供的網路封包加解密函數介面,根據函數介面的函數性質來分的話,可以分為「未導出的函數介面」和「導出的函數介面」。其中Chrome,Putty,SecureCrt以及WinSCP中的網路加解密介面是屬於「未導出的函數介面」,需要通過逆向工程來找到其Signature的位置,然後通過HOOK劫持。例如Mozilla Firefox使用了nss3.dll和nspr4.dll這兩個模塊中的加解密函數,nss3.dll中導出了PR_Read,PR_Write以及PR_GetDescType,後者導出了PR_Send和PR_Recv。但對於無法導出SSL_Read和SSL_Write函數的Chrome來說,要實現HOOK就很難了。

對於想劫持此類調用的人來說,主要問題是無法輕鬆地在巨大的chrome.dll文件中找到這些SSL函數。所以要在二進位文件中手動找到它們,就得想點妙招了。

從Chrome的源代碼入手

為了實現在二進位文件中找到SSL函數的目標,最好的入手點可能是Chrome的源代碼。關於Chrome的源代碼,你可以點此詳細了解,並輕鬆地搜索和瀏覽想要的源代碼。

在查看Chrome的源代碼時,你要注意到Google Chrome使用了boringssl,這是OpenSSL的一個分支項目,此項目可在Chromium源代碼中找到。

現在,我們必須找到我們需要的函數:SSL_read和SSL_write,並且我們可以輕鬆地在ssl_lib.cc文件中找到這兩個函數。

·SSL_read:

int SSL_read(SSL *ssl, void *buf, int num) {

int ret = SSL_peek(ssl, buf, num);

if (ret

return ret;

}

// TODO(davidben): In DTLS, should the rest of the record be discarded? DTLS

// is not a stream. See https://crbug.com/boringssl/65.

ssl->s3->pending_app_data =

ssl->s3->pending_app_data.subspan(static_cast(ret));

if (ssl->s3->pending_app_data.empty()) {

ssl->s3->read_buffer.DiscardConsumed();

}

return ret;

}

·SSL_write:

int SSL_write(SSL *ssl, const void *buf, int num) {

ssl_reset_error_state(ssl);

if (ssl->do_handshake == NULL) {

OPENSSL_PUT_ERROR(SSL, SSL_R_UNINITIALIZED);

return -1;

}

if (ssl->s3->write_shutdown != ssl_shutdown_none) {

OPENSSL_PUT_ERROR(SSL, SSL_R_PROTOCOL_IS_SHUTDOWN);

return -1;

}

int ret = 0;

bool needs_handshake = false;

do {

// If necessary, complete the handshake implicitly.

if (!ssl_can_write(ssl)) {

ret = SSL_do_handshake(ssl);

if (ret

return ret;

}

if (ret == 0) {

OPENSSL_PUT_ERROR(SSL, SSL_R_SSL_HANDSHAKE_FAILURE);

return -1;

}

}

ret = ssl->method->write_app_data(ssl, &needs_handshake,

(const uint8_t *)buf, num);

} while (needs_handshake);

return ret;

}

我們為什麼要看代碼?原因很簡單,就是在二進位文件中,我們可能會找到一些我們在源代碼中也能找到的東西,比如字元串或特定值。

要說明的是,此方法不僅適用於Chrome,還適用於其他工具如Putty或WinSCP。

SSL_write函數

即使SSL_read函數沒有提供有用的信息,我們也可以從SSL_write開始,因為,我們可以從中看到一些看起來很有用的東西。

OPENSSL_PUT_ERROR(SSL, SSL_R_UNINITIALIZED);

這是OPENSSL_PUT_ERROR宏:

// OPENSSL_PUT_ERROR is used by OpenSSL code to add an error to the error

// queue.

#define OPENSSL_PUT_ERROR(library, reason)

ERR_put_error(ERR_LIB_##library, 0, reason, __FILE__, __LINE__)

有些東西非常有用,比如:

1.ERR_put_error是一個函數調用;

2.reason是第二個參數,在我們的例子中SSL_R_UNINITIALIZED的值為226(0xE2);

3.__FILE__是ssl_lib.cc的實際文件名,完整路徑;

4.__LINE__是ssl_lib.cc文件中的當前行號;

所有這些信息都可以幫助我們找到SSL_write函數,其背後的原因你知道嗎?

1.我們知道了這是一個函數調用,因此參數(如reason,__FILE__和__LINE__)將被放置在堆棧(x86)上;

2.我們知道了這是reason(0xE2);

3.我們知道了__FILE__(ssl_lib.cc);

4.我們知道了__LINE__(在這個版本中是1060或0x424);

但是如果使用了不同的版本呢?那行號就可以完全不同。那麼,在這種情況下,我們必須看看Google Chrome如何使用BoringSSL。

我們可以在這裡找到特定版本的Chrome。例如,現在在x86上我用的就是版本65.0.3325.181(官方版本)(32位)。我們可以在這裡找到它的源代碼。所以接下來,我們必須找到BoringSSL代碼,但它看起來不在這裡。雖然如此,我們還是可以發現DEPS文件是非常的有用,並可以從中提取一些信息。

vars = {

...

"boringssl_git":

"https://boringssl.googlesource.com",

"boringssl_revision":

"94cd196a80252c98e329e979870f2a462cc4f402",

從以上代碼段中,你可以看到,我們的Chrome版本使用獲取BoringSSL,並使用此版本:94cd196a80252c98e329e979870f2a462cc4f402。基於此,我們可以在這裡獲得BoringSSL的確切代碼,並找到ssl_lib.cc文件。

現在,讓我們看看我們必須採取哪些步驟來獲取SSL_write函數地址:

1.在chrome.dll(.rdata)的只讀部分中搜索「ssl_lib.cc」文件名;

2.獲取完整路徑並搜索引用;

3.檢查對字元串的所有引用,並根據reason函數和行號參數找到正確的引用;

SSL_read函數

找到SSL_write函數並不困難,因為存在OPENSSL_PUT_ERROR,但我們沒有在SSL_read上使用它。現在,讓我們來看看SSL_read如何工作?

我們可以很容易地看到它調用SSL_peek:

int ret = SSL_peek(ssl, buf, num);

如下所示,我們還可以看到SSL_peek會調用ssl_read_impl函數。

int SSL_peek(SSL *ssl, void *buf, int num) {

int ret = ssl_read_impl(ssl);

if (ret

return ret;

}

...

}

而ssl_read_impl函數正試圖幫助我們:

static int ssl_read_impl(SSL *ssl) {

ssl_reset_error_state(ssl);

if (ssl->do_handshake == NULL) {

OPENSSL_PUT_ERROR(SSL, SSL_R_UNINITIALIZED);

return -1;

}

...

}

通過在代碼中搜索,我們可以發現ssl_read_impl函數只調用兩次,通過SSL_peek和SSL_shutdown函數,所以很容易找到SSL_peek。在我們找到SSL_peek之後,SSL_read就可以直接找到了。

Chrome 32位

既然我們有了關於如何找到SSL函數的總體思路,就讓我們來找到它們吧!

我們在本文使用的是x64dbg,但你也可以使用任何其他調試器。我們必須進入「內存」選項卡找到chrome.dll。整個過程,需要兩個步驟:

1.在反彙編程序中打開代碼部分,右鍵單擊「.text」並選擇「在反彙編程序中執行」;

2.在轉儲窗口中打開只讀數據部分,右鍵單擊「.rdata」並選擇「轉儲」;

現在,我們就必須在轉儲窗口中找到「ssl_lib.cc」字元串,然後點擊右鍵,選擇「Find Pattern」並搜索我們的ASCII字元串。此時,你應該得到一個單一的結果,雙擊它然後返回,直到找到ssl_lib.cc文件的完整路徑。右鍵單擊完整路徑的第一個位元組,如下面的屏幕截圖所示,然後選擇「查找引用」以查看我們可以找到它的位置(OPENSSL_PUT_ERROR函數調用)。

從上圖中,你大概能找到多個引用,但我們還必須一個個來試一下,才能地找合適的引用,查找結果如下。

我們來看最後一個例子,看看它是怎樣的?

6D44325C | 68 AD 03 00 00 | push 3AD |

6D443261 | 68 24 24 E9 6D | push chrome.6DE92424 | 6DE92424:"../../third_party/boringssl/src/ssl/ssl_lib.cc"

6D443266 | 6A 44 | push 44 |

6D443268 | 6A 00 | push 0 |

6D44326A | 6A 10 | push 10 |

6D44326C | E8 27 A7 00 FF | call chrome.6C44D998 |

6D443271 | 83 C4 14 | add esp,14 |

我們預期的完全一樣,這是一個帶有五個參數的函數調用,正如你可能知道的那樣,參數從右到左被推入棧中:

1. push 3AD:行號;

2. push chrome.6DE92424:我們的字元串,文件路徑;

3. push 44-:原因;

4. push 0:始終為0的參數;

5. push 10:第一個參數;

6.調用chrome.6C44D998 :調用ERR_put_error函數;

7.添加esp,1:清理堆棧

但是,0x3AD表示行號941,它位於「ssl_do_post_handshake」內部,因此它不是我們所需要的。

SSL_write

SSL_write在行號1056(0x420)和1061(x0425)上調用了該函數,因此我們需要在開始時通過push 420或push 425查找函數的調用,查找過程將需要幾秒鐘。

6BBA52D0 | 68 25 04 00 00 | push 425 |

6BBA52D5 | 68 24 24 E9 6D | push chrome.6DE92424 | 6DE92424:"../../third_party/boringssl/src/ssl/ssl_lib.cc"

6BBA52DA | 68 C2 00 00 00 | push C2 |

6BBA52DF | EB 0F | jmp chrome.6BBA52F0 |

6BBA52E1 | 68 20 04 00 00 | push 420 |

6BBA52E6 | 68 24 24 E9 6D | push chrome.6DE92424 | 6DE92424:"../../third_party/boringssl/src/ssl/ssl_lib.cc"

6BBA52EB | 68 E2 00 00 00 | push E2 |

6BBA52F0 | 6A 00 | push 0 |

6BBA52F2 | 6A 10 | push 10 |

6BBA52F4 | E8 9F 86 8A 00 | call chrome.6C44D998 |

我們可以在上面看到這兩個函數調用,但只是提到第一個是優化的。現在,我們只需要返回,直到找到一個看起來以函數開頭的內容。雖然該方法並不適用於其他函數,但它還是適用於本文的情況的,我們可以通過經典函數序言( classic function prologue)很容易地找到一個看起來以函數開頭的內容。

6BBA5291 | 55 | push ebp |

6BBA5292 | 89 E5 | mov ebp,esp |

6BBA5294 | 53 | push ebx |

6BBA5295 | 57 | push edi |

6BBA5296 | 56 | push esi |

讓我們在6BBA5291處放置一個斷點,看看當我們使用Chrome瀏覽某些HTTPS網站時會發生什麼?為了避免發生其他問題,我瀏覽一個沒有SPDY或HTTP/2.0的網站。

以下這個例子,就是當斷點被觸發時,可以在堆棧的頂部得到的內容。

06DEF274 6A0651E8 return to chrome.6A0651E8 from chrome.6A065291

06DEF278 0D48C9C0 ; First parameter of SSL_write (pointer to SSL)

06DEF27C 0B3C61F8 ; Second parameter, the payload

06DEF280 0000051C ; Third parameter, payload size

如果你要右鍵單擊第二個參數,然後選擇「Follow DWORD in Dump」,此時,你應該會看到一些純文本數據,例如:

0B3C61F8 50 4F 53 54 20 2F 61 68 2F 61 6A 61 78 2F 72 65 POST /ah/ajax/re

0B3C6208 63 6F 72 64 2D 69 6D 70 72 65 73 73 69 6F 6E 73 cord-impressions

0B3C6218 3F 63 34 69 3D 65 50 6D 5F 66 48 70 72 78 64 48 ?c4i=ePm_fHprxdH

SSL_read

現在我們來看看SSL_read函數,此時,應該可以從ssl_read_impl函數中找到對「OPENSSL_PUT_ERROR」的調用。該調用在第962行(0x3C2)中可用。讓我們再看一遍結果並找到該調用。

6B902FAC | 68 C2 03 00 00 | push 3C2 |

6B902FB1 | 68 24 24 35 6C | push chrome.6C352424 | 6C352424:"../../third_party/boringssl/src/ssl/ssl_lib.cc"

6B902FB6 | 68 E2 00 00 00 | push E2 |

6B902FBB | 6A 00 | push 0 |

6B902FBD | 6A 10 | push 10 |

6B902FBF | E8 D4 A9 00 FF | call chrome.6A90D998 |

現在,我們應該能很容易找到函數的開始部分。此時,右鍵單擊第一條指令(push EBP),選擇「查找引用」和「選定地址(es)」。

此時,我們應該只能找到一個函數調用,它應該是SSL_peek。找到SSL_peek的第一條指令並重複剛才的步驟。此時,我們應該只能得到一個結果,即從SSL_read調用SSL_peek。

6A065F52 | 55 | push ebp | ; SSL_read function

6A065F53 | 89 E5 | mov ebp,esp |

...

6A065F60 | 57 | push edi |

6A065F61 | E8 35 00 00 00 | call chrome.6A065F9B | ; Call SSL_peek

此時,讓我們放置一個斷點,這樣,我們可以在正常的調用中看到以下內容。

06DEF338 6A065D8F return to chrome.6A065D8F from chrome.6A065F52

06DEF33C 0AF39EA0 ; First parameter of SSL_read, pointer to SSL

06DEF340 0D4D5880 ; Second parameter, the payload

06DEF344 00001000 ; Third parameter, payload length

現在,我們應該右鍵單擊第二個參數,然後在按下「Execute til return」按鈕之前選擇「Follow DWORD in Dump」,以便在函數結束時終止調試器,因此在緩衝區中讀取數據之後。我們應該能夠在轉儲窗口中看到純文本數據,這時,我們就可以選擇有效載荷了。

0D4D5880 48 54 54 50 2F 31 2E 31 20 32 30 30 20 4F 4B 0D HTTP/1.1 200 OK.

0D4D5890 0A 43 6F 6E 74 65 6E 74 2D 54 79 70 65 3A 20 69 .Content-Type: i

0D4D58A0 6D 61 67 65 2F 67 69 66 0D 0A 54 72 61 6E 73 66 mage/gif..Transf

總結

如果我們從二進位文件中的源代碼入手,就很容易Hooking Chrome瀏覽器的SSL函數來讀取SSL通信數據,這種方法應該適用於大多數開源應用程序。由於x64版本非常相似,唯一的區別是彙編代碼,因此在此不再贅述。

但是,請注意,Hooking這些函數可能會導致瀏覽器不穩定的運行和可能發生的崩潰。


喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 嘶吼RoarTalk 的精彩文章:

報名通道即將關閉!抓住尾巴快上車啦!
在線破解SNMP密碼的多種方法

TAG:嘶吼RoarTalk |