Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 使用Android系統自帶的VpnService截取流量

使用Android系統自帶的VpnService截取流量

編輯:關於Android編程

最近在做Android下的流量分析的一個功能,查閱了眾多資料,在手機不用Root的情況下,可以設置代理,對流量分析。也可以使用Android系統提供的VpnService對流量進行截取。

關於VpnService的具體流程,下面進行簡單介紹。

從Android 4.0開始,系統自帶了一個在設備上建立VPN連接的解決方案,而且不需要root權限。關於VpnService系統自帶的一個例子,可以參考系統自帶的ToyVpn。這個是SDK源碼裡的一個例子。但這個例子是使用VPN進行遠程連接的例子,對於流量分析功能,沒有提到。要想使用VpnService進行抓包流量分析,需要對VpnService有詳細的了解。

VpnService原理

這裡我參考了博主Roland_Sun 的一篇博文,對VpnService進行了詳細的分析。

在Android設備上,如果已經使用了VpnService框架,建立起一條從設備到遠程服務器的VPN連接,那麼數據包在設備上大致經歷了如下四個過程的轉換:

\

 

1、應用程序使用Socket,將相應的數據包發送到真實的網絡設備上,一般移動設備只有無限網卡,因此是發送到真實的Wifi設備上的。

2、Android系統通過iptables,使用NAT,將所有的數據包轉發到TUN虛擬網絡設備上去,端口是tun0;

3、VPN程序通過打開/dev/tun設備,並讀取該設備上的數據,可以獲得所有的轉發到Tun虛擬網卡上的數據包。因為設備上的所有IP包都會被NAT轉成源地址是tun0端口發送的,所以也就是說你的VPN程序可以獲得進出該設備的幾乎所有的數據(也有例外,不是全部,比如回還數據無法獲得)。

4、VPN數據可以做一些處理,然後將處理過後的數據包,通過真實的網絡設備發送出去。為了防止發送的數據包再被轉發到TUN虛擬網絡設備上,VPN程序所使用的socket必須先被明確綁定到真實的網絡設備上去。

代碼實現:

要實現Android設備上的VPN程序,一般需要分別實現一個繼承Activity類的自帶UI的客戶端和一個繼承自VpnService類的服務程序

申明權限

 

要想讓你的VPN程序正常運行,首先必須要在AndroidManifest.xml中顯示聲明使用:

“android.permission.BIND_VPN_SERVICE”權限。

客戶程序實現

客戶程序一般要首先調用VpnService.prepare函數:

Intentintent=VpnService.prepare(this);if(intent!=null){startActivityForResult(intent,0);}else{onActivityResult(0,RESULT_OK,null);}

目前Android只支持一條VPN連接,如果新的程序想建立一條VPN連接,必須先中斷系統中當前存在的那個VPN連接。

同時,由於VPN程序的權利實在太大了,所以在正式建立之前,還要彈出一個對話框,讓用戶點頭確認。

\

 

VpnService.prepare函數的目的,主要是用來檢查當前系統中是不是已經存在一個VPN連接了,如果有了的話,是不是就是本程序創建的。

如果當前系統中沒有VPN連接,或者存在的VPN連接不是本程序建立的,則VpnService.prepare函數會返回一個intent。這個 intent就是用來觸發確認對話框的,程序會接著調用startActivityForResult將對話框彈出來等用戶確認。如果用戶確認了,則會關閉前面已經建立的VPN連接,並重置虛擬端口。該對話框返回的時候,會調用onActivityResult函數,並告之用戶的選擇。

如果當前系統中有VPN連接,並且這個連接就是本程序建立的,則函數會返回null,就不需要用戶再確認了。因為用戶在本程序第一次建立VPN連接的時候已經確認過了,就不要再重復確認了,直接手動調用onActivityResult函數就行了。

如果返回結果是OK的,也就是用戶同意建立VPN連接,則將你寫的,繼承自VpnService類的服務啟動起來就行了。

當然,你也可以通過intent傳遞一些別的參數。

服務程序實現

服務程序必須要繼承自android.net.VpnService類:

public class MyVpnService extendsVpnService ...

VpnService類封裝了建立VPN連接所必須的所有函數,後面會逐步用到。

建立鏈接的第一步是要用合適的參數,創建並初始化好tun0虛擬網絡端口,這可以通過在VpnService類中的一個內部類Builder來做到:

Builder builder = new Builder();

builder.setMtu(...);

builder.addAddress(...);

builder.addRoute(...);

builder.addDnsServer(...);

builder.addSearchDomain(...);

builder.setSession(...);

builder.setConfigureIntent(...);

 

ParcelFileDescriptor interface =builder.establish();

可以看到,這裡使用了標准的Builder設計模式。在正式建立(establish)虛擬網絡接口之前,需要設置好幾個參數,分別是:

1)MTU(MaximunTransmission Unit),即表示虛擬網絡端口的最大傳輸單元,如果發送的包長度超過這個數字,則會被分包;一般設為1500

2)Address,即這個虛擬網絡端口的IP地址;這個地址可以去查查,我參考的360流量衛士裡面的地址為192.168.*.*;好多也使用10.0.2.0;不確定,都可以試試。

3)Route,只有匹配上的IP包,才會被路由到虛擬端口上去。如果是0.0.0.0/0的話,則會將所有的IP包都路由到虛擬端口上去;

4)DNSServer,就是該端口的DNS服務器地址;

5)Search Domain,就是添加DNS域名的自動補齊。DNS服務器必須通過全域名進行搜索,但每次查找都輸入全域名太麻煩了,可以通過配置域名的自動補齊規則予以簡化;

6)Session,就是你要建立的VPN連接的名字,它將會在系統管理的與VPN連接相關的通知欄和對話框中顯示出來

\

 

7)Configure Intent,這個intent指向一個配置頁面,用來配置VPN鏈接。它不是必須的,如果沒設置的話,則系統彈出的VPN相關對話框中不會出現配置按鈕。

最後調用Builder.establish函數,如果一切正常的話,tun0虛擬網絡接口就建立完成了。並且,同時還會通過iptables命令,修改NAT表,將所有數據轉發到tun0接口上

這之後,就可以通過讀寫VpnService.Builder返回的ParcelFileDescriptor實例來獲得設備上所有向外發送的IP數據包和返回處理過後的IP數據包到TCP/IP協議棧:

// Packets received need to be written tothis output stream.

FileOutputStream out = newFileOutputStream(interface.getFileDescriptor());

 

// Allocate the buffer for a single packet.

ByteBuffer packet =ByteBuffer.allocate(32767);

...

// Read packets sending to this interface

int length = in.read(packet.array());

...

// Write response packets back

out.write(packet.array(), 0, length);

ParcelFileDescriptor類有一個getFileDescriptor函數,其會返回一個文件描述符,這樣就可以將對接口的讀寫操作轉換成對文件的讀寫操作。

每次調用FileInputStream.read函數會讀取一個IP數據包,而調用FileOutputStream.write函數會寫入一個IP數據包到TCP/IP協議棧。

這其實基本上就是這個所謂的VpnService的全部了,是不是覺得有點奇怪,半點沒涉及到建立VPN鏈接的事情。這個框架其實只是可以讓某個應 用程序可以方便的截獲設備上所有發送出去和接收到的數據包,僅此而已。能獲得這些數據包,當然可以非常方便的將它們封裝起來,和遠端VPN服務器建立 VPN鏈接,但是這一塊VpnService框架並沒有涉及,留給你的應用程序自己解決。

還有一點要特別解釋一下,一般的應用程序,在獲得這些IP數據包後,會將它們再通過socket發送出去。但是,這樣做會有問題,你的程序建立的 socket和別的程序建立的socket其實沒有區別,發送出去後,還是會被轉發到tun0接口,再回到你的程序,這樣就是一個死循環了。為了解決這個問題,VpnService類提供了一個叫protect的函數,在VPN程序自己建立socket之後,必須要對其進行保護:

protect(my_socket);

其背後的原理是將這個socket和真實的網絡接口進行綁定,保證通過這個socket發送出去的數據包一定是通過真實的網絡接口發送出去的,不會被轉發到虛擬的tun0接口上去。

好了,Android系統默認提供的這個VPN框架就只有這麼點東西。

最後,簡單總結一下:

1)VPN連接對於應用程序來說是完全透明的,應用程序完全感知不到VPN的存在,也不需要為支持VPN做任何更改;

2)並不需要獲得Android設備的root權限就可以建立VPN連接。你所需要的只是在你應用程序內的AndroidManifest.xml文件中申明需要一個叫做“android.permission.BIND_VPN_SERVICE”的特殊權限;

3)在正式建立VPN鏈接之前,Android系統會彈出一個對話框,需要用戶明確的同意;

4)一旦建立起了VPN連接,Android設備上所有發送出去的IP包,都會被轉發到虛擬網卡的網絡接口上去(主要是通過給不同的套接字打fwmark標簽和iproute2策略路由來實現的);

5)VPN程序可以通過讀取這個接口上的數據,來獲得所有設備上發送出去的IP包;同時,可以通過寫入數據到這個接口上,將任何IP數據包插入系統的TCP/IP協議棧,最終送給接收的應用程序;

6)Android系統中同一時間只允許建立一條VPN鏈接。如果有程序想建立新的VPN鏈接,在獲得用戶同意後,前面已有的VPN鏈接會被中斷;

7)這個框架雖然叫做VpnService,但其實只是讓程序可以獲得設備上的所有IP數據包。通過前面的簡單分析,大家應該已經感覺到了,這個所 謂的VPN服務,的確可以方便的用來在Android設備上建立和遠端服務器之間的VPN連接,但其實它也可以被用來干很多有趣的事情,比如可以用來做防火牆,也可以用來抓設備上的所有IP包。

以上總結,大部分為Roland_Sun的精心總結。對VpnService分析的非常透徹,跟著博主的總結,基本上實現了上圖中的(1)、(2)、(3)步驟,但所有的數據都被攔截在了自己搭建的服務器裡面。想要實現第(4)步,即將攔截到的數據進行轉發到真實的網卡上面,並獲取到回傳數據實現數據的流通程序,這個過程就有點復雜,這涉及到TCP、IP、UDP協議包的拆包、裝包分析,將協議包數據拆開後,得到目的地址、源地址、源和目的端口號,並protect()下,將數據轉發至真實網卡,實現數據的流通。

下面我僅提供一個1、2、3步驟的小demo VpnServiceDemo。中間的兩個線程是攔截數據並拋出數據的過程,在日志打印就可以看到數據包的各種信息。第4步的工作量有點大,有精力的可以繼續完成。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved