網路層攔截可選項

【1】客戶端應用層實現 -> 【2】客戶端網路庫實現 -> 【3】系統庫libc.so -> 【4】(從這裡開始是內核態) syscall -> 【5】tcp/ip協議棧 -> 【6】NFQUEUE-> 【7】BPF -> 【8】tun adapter (假網卡) -> 【9】真網卡 -> 【10】外部網路設備

【1】客戶端應用層實現

client可以在應用層直接支持proxy: 鏈接模塊直接實現Proxy(Request)並發送到proxy,libcurl是這樣做的, 也可以交給更底層的模塊去處理, 在這之前, 先列出一個ip包在linux系統下應用層調用send()介面後經歷哪些模塊到達網卡, 可能會不完整, 僅列出下文會引用到的, 模塊之間可理解為調用關係:

想要把本來要發給server的包發送給proxy, 除了應用層自己在發送時修改包和目標地址, 還可以在包到達真網卡之間的某一層攔截並且修改包內容, 這樣做的好處有兩點: 應用層模塊不必關心代理細節甚至不知道是否代理, 代碼更簡潔; 在遠離應用層的代理實現可以被不同的進程載入, 甚至可以同時應用在全局所有進程, 徹底對上層透明.

【2】客戶端網路庫實現

libcurl等網路庫都有良好的(特別是Http)的代理支持, 在應用層設置代理伺服器後可以無縫使用, python, java 使用的網路庫也有代理支持. 從使用者的角度來看無需詳述. 提到這一層是因為無論是libcurl, 還是python的網路模塊都還不會直接發數據到網卡, 除了go以外大家普遍使用libc.so的send()介面去發送數據到套接字, 拋開go不談, 我們來看libc.so上可以做什麼.

【3】系統庫libc.so

網路庫也不是自己真正去發包的, 而是調用系統庫send()方法去發送. 可以在進程載入時通過更改環境變數LD_PRELOAD載入一個假的lib_fake_c.so讓進程當成libc.so使用. 在這個假的lib_fake_c.so里攔截send()等socket io介面, 封裝代理包並調用真的libc.so的send()介面發送給proxy, 就可以不修改產品代碼直接使用代理了. 這個假的libc.so不光可以讓多個產品透明地使用同一份代理實現, 而且在他們不想使用代理時不需要重新編譯, 重新啟動, 載入真的libc.so就可以切換回正常的網路使用.

  • proxychains的實現就是使用這種方法在終端進行代理的.
  • 微信的後台libco.so也是用這種方法實現攔截libc介面的.

進程啟動時:

export LD_PRELOAD="./lib_fake_c.so"n./my_servicen

lib_fake_c.so

#define HOOK_SYS_FUNC(name) if( !g_sys_##name##_func ) { g_sys_##name##_func = (name##_pfn_t)dlsym(RTLD_NEXT,#name); }nnint close(int fd)n{n HOOK_SYS_FUNC( close );nn if( !co_is_enable_sys_hook() )n {n return g_sys_close_func( fd );n }nn free_by_fd( fd );n int ret = g_sys_close_func(fd);nn return ret;n}n

【4】syscall

其實libc.so還在用戶態運行, 操作系統內核和用戶進程之間有一套介面叫做syscall, libc調用syscall進入內核態操作socket, (send() (用戶態) -> syscall (中斷>>send對應的操作介面>>內核) -> ...). 在內核態還有一個介面叫做ptrace, 它可以攔截syscall 在內核中對socket操作(其實是可以攔截所有內核態操作, 此處只關心send), 攔截了以後切換回用戶態, ptrace的實現就像觀察者模式一樣, 收到了關心的觀察信號後在用戶態對這個send操作做修改, ptrace的返回會切換回內核態繼續發送這個篡改過的內容. 從內核態-用戶態-內核態的操作不難推斷出, 這種方法是有一定性能損耗的, (todo) gdb就是這種方法攔截所有內核態的操作供調用者單步調試, 在debug的環境下性能損耗可以無視, 在生產環境用這種方法來做代理就需要斟酌了. 我覺得不會有人用這種方式實現代理的.

【5】tcp/ip協議棧

syscall之後這個包的操作就到達了協議棧. 協議棧上的網路包的操作都會經過iptables的處理, iptables的一個著名功能就是ip包的轉發規則nat攔截應用層的鏈接. 我們在這一層可以做的事情就是轉發應用層的某個鏈接的包到本地hack_server或者異地hack_server, 在這個hack_server里收到被轉發來的包, 讀取它的真實src和真實dst, 進行代理協議封裝後發送給proxy.

iptables -t nat -I OUTPUT -p tcp ! -s 10.1.2.3 -j DNAT --to-destination 10.1.2.3:8319n

iptables -t nat -I PREROUTING -p tcp ! -s 10.1.2.3 -j DNAT --to-destination 10.1.2.3:8319n

特別注意 -j DNAT 和 -j REDIRECT 是不同的。PREROUTING 只能用 DNAT 到本機的一個別名 ip(10.1.2.3 比如),沒法用 -j REDIRECT 到 127.0.0.1

【6】NFQUEUE

iptables除了可以把包轉發給特定地址特定埠的監聽服務外, 還可以轉發到一個叫做NFQUEUE的目標上,

iptables -A INPUT -j NFQUEUE --queue-num 0n

在包被轉發到queue num=0 NFQUEUE隊列之後, 某個使用libnetfilter_queue 連接隊列0的進程從內核態獲取到包的信息, 對包的去向進行裁決. NFQUEUE之後的工作是在用戶態完成的, 所以這裡也有相應的性能損耗. fqting使用了NFQUEUE對包的內容進行了混淆, 混淆後的包的參數使之在IDS上重組時產生錯位, 重組流不為中間路由所知, 在proxy ip不被封禁的情況下可以繞過封禁內容的防火牆.

【7】BPF

在正常情況下, 下一步協議棧就會把包發給網卡驅動了, 但是在有BPF監聽的情況下, 網路包會被發送給BPF進行篩選, 並把符合篩選條件的包拷貝到過濾條件對應的進程的緩存. tcpdump從這個緩衝區里讀出包的內容, 但是不能修改, 因為該緩衝區的數據是內核數據包的拷貝, 修改它並不能影響內核中的數據.

這一層除了 tcpdump 的 PF_PACKET 的實現,還可以走 raw_socket 在三層監聽。另外 AF_RING 也是一個選項。

【8】虛擬網卡

協議棧後網路包被發送給網卡驅動, 此處可以使用tun/tap驅動, tun像一個網卡那樣接收tcp/ip協議棧處理好的網路分包, 但並不真正發送, 而是轉而把這個網路包發送給任何一個使用tun/tap驅動的進程,由進程重新處理後再發到物理鏈路中, tap和tun類似,不過在2層. 通過這種方式在監聽進程中可以改變網路包發送給proxy. OpenVpn就是這種方式實現的全局代理.

虛擬網卡可以考慮linux的network namespace,可以在本機隔離出多套環境來。

【9】真網卡

這個不太懂,沒想過。

【10】外部網路設備

在外部設備攔截關鍵是要把流量倒過去。這裡有幾種方法

  • dhcp:搶答 dhcp 協議,給一個錯誤的 gateway。
  • arp:直接把 gateway 的對應的 mac 地址給注入到同網段下的其他機器的 arp cache里。某軟體的 pick & play 就是這麼實現的

  • dns:修改dns把域名解析改了。

推薦閱讀:

不越獄就能監控蘋果手機? iCloud備份成漏洞

TAG:计算机网络 | 网络安全 |