三個星期前的星期五(2024年7月19日),電腦安全公司 CrowdStrike 在更新防駭客軟體 Falcon 的配置資料時捅了一個大簍子,導致全球超過 850多萬臺 Windows 電腦崩潰,使用者祭出重啟大法也無濟於事。
這一事故涉及大批機場、學校、醫院、超市。公司等客戶。《財富》(Fortune)雜誌估計事故給財富 500 強公司造成了 54 億美元經濟損失,給其它公司造成的損失和次生傷害更是罄竹難書。
5 只獨角獸啊,咣噹一下就給乾沒了。

CrowdStrike 自己更慘,事發之後,股價跌了 20% 多,市值蒸發了 150 億美元。

事故發生後,CrowdStrike 釋出了一系列簡短宣告,對事故原因做了大致的解釋,但沒有透露太多細節,只是說在調查完畢之後會公佈結果。
上週他們公佈了一個初始報告。這個星期二,正式的報告來了。
在這瞬息萬變的世界裡,群眾對任何事情都只有五分鐘的關注(誰還記得姜萍?)。與事故發生時鋪天蓋地的報道比,這一次CrowdStrike的宣告可謂波瀾不驚。顯然,熱度已經迅速消退。
然而,對 IT 從業人員來說這次災難是一個千載難逢的富礦,不容錯過。
不要以為這樣的事只會發生在不靠譜的公司。CrowdStrike 犯過的錯,我們很多人也犯過。不同的是,我們的運氣比較好,沒有在屋漏的時候遇上連夜雨。
這份事故報告值得細細品,慢慢讀。通過了解 CrowdStrike 的巨坑是怎樣挖成的,我們可以顧影自憐以史為鑑,避免成為下一個萬人唾罵的物件。
~~~~
CrowdStrike 的報告全文很短,不過 12 頁,可以很快看完。
在此之前,微軟負責企業和作業系統安全的副總裁 David Weston 已經在微軟的安全部落格上發文《Windows安全工具最佳實踐》(Windows Security best practices for integrating and managing security tools),委婉指出 CrowdStrike 這次事故的內情。
把雙方的宣告放在一起看很有意思。我們可以學習到如何文明甩鍋和不帶髒字吐槽豬隊友。
比如,這次事故之所以後果如此嚴重,是因為出錯的軟體以核心模式執行,享有最高特權。要是出錯的是使用者模式的軟體,絕不至於讓系統整個宕掉。對此微軟義正辭嚴地指出:
“今天,安全工具可以在安全性和可靠性之間取得平衡。例如,安全供應商可以使用執行在核心模式下的最小感測器進行資料收集和執行,從而減少因可靠性問題而暴露的風險。其餘關鍵產品功能,包括管理更新、解析內容和其他操作,可以在使用者模式中隔離進行,這樣在出現問題時更易於恢復。…… Windows 提供了幾種使用者模式保護方法來防止篡改,如虛擬化基礎安全 (VBS) 隔離區和受保護程序,供應商可以利用這些方法保護其關鍵安全程序。Windows 還提供了事件跟蹤 (ETW) 事件和反惡意軟體掃描介面這樣的使用者模式介面。這些強大的機制可以用於減少建立安全解決方案所需的核心程式碼量,從而實現安全性和可靠性的平衡。”
(解讀:兄弟姐妹們呀,不是我家 Windows 不安全啊,是 CrowdStrike 沒用好啊!明明我們有那麼多方法在使用者模式下實現安全功能,他們就是不用啊!)
CrowdStrike 則深情回應:
“當新版 Windows 對在使用者模式下執行安全功能提供更多支援時,我們會利用這些支援。Windows 生態在支援使用者模式下的安全產品上還任重道遠。”
(解讀:老鄉們啊,別聽微軟畫餅啊!那當不了吃的啊!現在而今眼目下核心模式還是要的哈!)

兩大美國甩鍋高手尖峰對決
在解讀 CrowdStrike 事故報告之前,先更正一下我上一篇文章《如何寫出萬人唾罵的軟體 – 史上最大Windows藍色畫面事故分析》中的一些錯誤和不準確的地方:
-
中國躲過一劫,根本原因不是中國對系統國產化的推進,而是因為CrowdStrike不對中國使用者提供服務。
-
造成事故的更新沒有經過 Windows 的更新發布平臺,是 CrowdStrike 自己的行為。而Windows 自身的更新是給了使用者選擇權的。(但這不代表微軟沒有監管不力的責任。)
-
微軟把系統驅動的核心許可權開放給 CrowdStrike 這樣的第三方,是因為自己跟歐盟有一個反壟斷協議。微軟也不想,微軟很委屈。
-
事故的直接導因不是空指標訪問,而是陣列越界造成的垃圾指標訪問。
好了,開講。
~~~~
在介紹事故細節前,我們先把事情的背景和來龍去脈大致理一遍,讓大家有個直觀的理解。
首先,CrowdStrike 是做什麼的?
它是一家總部位於美國德州的電腦安全公司,成立於 2011 年,為電腦提供防病毒防駭客服務。你就當它是美國的 360 安全衛士吧。這次出事的是它的 Falcon 安全軟體的 Windows 版本。
CrowdStrike 跟微軟又是什麼關係?
前者提供的安全服務是對後者作業系統的補充。我們知道 Windows 自帶微軟防衛者(Microsoft Defender)防毒軟體,提供了基本的安全功能。CrowdStrike 的賣點是它比微軟“更懂安全”(類似於百度比谷歌“更懂中文”)。
Falcon 是一款雲集成度非常高的產品,由 CrowdStrike 的伺服器和執行在使用者機器上的防毒軟體協同工作。
Falcon 客戶端分兩大塊:邏輯部分和資料部分。
負責邏輯的部分叫“感測器”(sensor),取這名的意思是大兄弟在看著你,系統出了啥么蛾子它都知道。
資料部分就是感測器配置檔案,又叫通道檔案。這些檔案定義了各種檢測駭客攻擊的規則。
感測器的程式碼分兩大模組:配置內容直譯器和檢測引擎。前者負責解析配置檔案,後者負責執行配置檔案中的規則。它們都有光明的未來最高級別的特權。

CrowdStrike Falcon 安全軟體架構
感測器之所以有特權,主要是因為從理論上說這樣可以讓防禦更可靠:它可以繳病毒的槍,而不是被病毒繳了槍。
感測器做的就是檢測系統動態,再按配置檔案中定義的規則對檢測到的資訊做出反應。比如發現一些程式在幹不該乾的事,就會報警並殺死這個執行中的程式。
使用者端的 Falcon 軟體每天都會接收伺服器發來的指令,更新配置檔案。
這種設計是因為電腦安全是一場資訊不對等的攻防遊戲,使用者在明處,駭客在暗處。新的攻擊手段層出不窮,守方不得不活到老,學到老(但也可能像 7/19 事件裡一樣學廢了)。
每當 CrowdStrike 有了新的發現和改進,就會向用戶傳送配置更新,好挫敗最新的攻擊技術(結果這一次真的好挫)。
注意:頻繁更新的是配置檔案,不是感測器軟體本身。因為感測器有特權,微軟對它的釋出和更新有貌似嚴格的控制(下面會解釋為什麼說是“貌似”),以免它有意無意地幹壞事。
每次更新感測器軟體,都必須透過微軟的測試和認證,少則幾天,多則幾個月,等不起啊。
而微軟認為配置檔案沒那麼重要,所以允許第三方軟體廠商自行更新(下面我們會分析這種想法為什麼是幼稚的)。於是, CrowdStrike 大部分時候都是透過更新配置檔案來讓使用者端的軟體與時俱進。
~~~~
7月19日發生了什麼?
那天 04:09 UTC,CrowdStrike 為 Falcon 釋出了一批新的配置檔案,用於對付新的駭客攻擊手段。
看起來,這不過是又一次常規操作。這種操作,CrowdStrike 平均每天都要做幾次,就像呼吸一樣自然。
但這一次呼吸,讓更新後的系統立馬斷氣了。
在 2024年7月19日星期五04:09 UTC 至 2024年7月19日星期五05:27 UTC 期間線上接收了更新的、執行 Falcon 軟體7.11 及以上版本 的 Windows 主機全部中招。
發現事故後,CrowdStrike 在 2024年7月19日星期五05:27 UTC 撤回了肇事更新,但為時已晚:數百萬電腦已經徹底癱瘓,無法和網路建立任何聯絡。
至此,只有一個方法能讓它們起死回生:讓客戶手動重啟機器並手動刪除有問題的配置檔案。
中招公司的系統管理員們熱淚盈眶:等了十年,我還以為資本家根本把我忘了。想不到一張衛生紙,一個管理員都有他的用處啊。

~~~~
先前大家猜測的事故原因是 CrowdStrike 系統驅動程式出現了空指標訪問(null pointer dereference)。
指標是電腦軟體術語,就是某段資料在記憶體中的位置,你可以把它想象成門牌號。透過指標,我們就可以從記憶體中指定的地點讀出特定的資料。
空指標是一個特殊的指標,指向一個特殊的不能訪問的地址(比如“前門大街 0 號”,而前門大街根本沒有 0 號),通常用來表示“資料不存在”。
空指標訪問,就是軟體出了邏輯錯誤,強行訪問不能訪問的地址,導致系統崩潰。
CrowdStrike 公佈的事故原因和大家猜測的略有不同。倒是沒有訪問空指標,而是因為陣列下標越界得到了一個垃圾指標,又透過這個垃圾指標訪問了一個不能訪問的地址。總之跟訪問空指標半斤八兩,結果都是系統崩潰。
陣列下標越界也是一種常見的程式設計錯誤:一片資料,一共 N 個元素,放在連續的記憶體裡,好比 N 家人住在同一條街上,門牌號相連。假比第一家是 8 號,第二家就是 9 號,第三家是 10 號,依此類推。如果到第 N + 1 家串門,就是下標越界,擅闖私宅。
CrowdStrike 的問題是:明明只有 N 個元素,程式碼偏偏要訪問第 N + 1 個。
越界的時候,程式沒有立即跑飛,還在走既定流程。
前 N 家每家都存了一個指標。第 N + 1 家還沒有裝修好,家裡一堆垃圾。但是驅動程式不管,把它家裡的垃圾當成指標,一看,上面寫的是“後海衚衕 9527 號”。
這程式倒也負責,先檢查了這個地址是不是空指標。發現不是,便放心去後海衚衕 9527 取貨。
可地圖上找不到這個地址。
於是 Windows 知道壞了 – 掌握最高許可權的系統驅動程式出現了無釐頭行為,除了揮淚自盡別無他法。否則接下來不知還會出現啥離譜的事。

~~~~
那麼下標越界又是怎麼發生的呢?這就要從感測器配置檔案的內容講起了。
每個配置檔案都記錄了一堆
規則
。每條規則定義瞭如何發現和處理某種攻擊。比如:“要是程序試圖刪除根目錄,就殺死程序”。
規則本身不長眼睛,看不到系統的監控資料。那它如何能發現攻擊呢?沒關係,檢測引擎是 Falcon 的眼睛,會把它觀測到的系統狀態以
引數(parameter)
的形式傳給配置內容直譯器,直譯器就可以執行規則了。
出於某種原因,CrowdStrike 把規則不叫規則,叫
模版(template)
。比如,
模版型別(template type)
定義了規則的格式(比如,有幾個引數,每個引數都是什麼意思),
模版例項(template instance)
則是一條具體的規則。每條規則都必須遵循一個事先規定的格式,否則感測器就不知道如何解讀它。
學過程式設計的同學應該已經看出來了:模版型別和模版例項的關係,就是型別和變數的關係。每個變數都必須有一個型別。

配置檔案內容
在上面的例子裡,配置檔案裡放了 5 個模版例項,前三個的型別是 A,後兩個的型別是 B。
這次事故的根本原因,是感測器內部對模版型別的格式理解出現了分歧。
具體地說,7/19 的配置檔案更新引入了兩個新的 A 型模版。Falcon 感測器在得到新的配置檔案後馬不停蹄地開始解釋執行其中的模版。
檢測引擎以為 A 型模版有 20 個引數,便整理好 20 個引數傳給直譯器。而
A 型模版其實有 21 個引數。
直譯器
出於對檢測引擎的一萬個信任(都是自家人,你辦事,我放心!),也就沒有檢查一下自己得到的引數到底是不是 21 個。
這兩個模組都該打屁股:檢測引擎把引數給錯了,直譯器在執行一條規則前沒有檢查前提條件。但凡任何一個模組把自己的工作做到位了,系統都不會崩潰。


阿呆和阿傻,半斤八兩
其實,這兩個邏輯錯誤從幾個月以前就存在了,只是一直沒有哪個 A 型模版用到第 21 個引數,所以一直沒有觸發。7月19日,爆雷了。
~~~~
問題又來了:這麼重要的系統軟體,按說在釋出前都要經過嚴格的測試。這麼明顯的臭蟲是如何從層層測試中殺出重圍的呢?

其實要分成兩個問題:
第一,包含這些邏輯錯誤的感測器軟體是如何透過微軟的“嚴格”認證的?
第二,觸發這些錯誤的資料(新的配置檔案)是如何透過 CrowdStrike 的測試的?
先說第一個。
每次 Falcon 升級確實都要經過微軟的 Windows Hardware Quality Labs(WHQL)認證。這個認證說起來好生厲害,基本上你能想到的各種手段都用上了:功能測試,壽命測試,壓力測試(包括故障注入),模糊測試,效能測試,靜態分析,等等等等。能透過這些考驗,那還不得是人中呂布馬中赤兔啊?
然而,這一系列令人眼花繚亂的操作並沒有抓住這隻臭蟲,因為:在全部這些測試時使用的都是當時已經發布的配置檔案。
這就有點搞笑了。我們知道,Falcon 系統的行為是受三大因素影響的:軟體程式碼,配置檔案,和系統監測資料。任何一個因素變了,結果都可能不同。所以,在認證某個版本的感測器軟體的時候,我們必須試圖證明無論配置檔案和監測資料如何變化,系統都會屹立不倒。(劃重點:關鍵詞是“無論……都會……”)
然而,微軟僅僅看到 Falcon 系統在配合當前配置檔案使用時工作正常就蓋章放行,實在是神經過於大條了。
再去看那些花費在 WHQL 認證上的大量人力物力,shit 上雕花的即視感撲面而來。
測試好是好,但從其本質上來說,測試只能證明系統有錯,不能證明系統無錯。更多的測試可以提升我們對系統正確性的信心,但要是測試的策略錯了,卷死也無益。
再看第二個問題:有問題的配置檔案又是如何透過 CrowdStrike 測試的?
因為配置檔案要求一日三更,對它的測試力度不可能和 WHQL 一樣。即便如此,像這種開機即崩的大臭蟲沒有被測試發現也是匪夷所思。
首先,這說明 CrowdStrike 沒有做端到端(end-to-end)測試,最多隻做了單元(unit)測試和整合(integration)測試。
-
單元測試,測的是某個模組在跟外界隔離的情況下是否能正常工作:如果輸入是 ABC,輸出是不是 EFG?
-
整合測試,測的是兩個或多個模組組合在一起能否正常協同工作。它能揪出一些單元測試暴露不了的問題,比如一個模組向另一個模組傳錯了引數。
-
端到端測試,測的是一個真實系統中全部模組在一起是否正常工作。它又能發現一些在整合測試時難以發現的問題。這次要是有端到端測試,肯定不會讓有錯的配置檔案透過。
從單元到整合再到端到端,維護和執行測試的成本越來越高,所以通常建議這三類測試的佔比是大致 70 比 20 比 10。端到端測試可以少一點,但不能沒有,尤其是像安全軟體這樣極端重要的系統。

三類測試的推薦佔比
其次,其實 CrowdStrike 的測試曾經發現了問題,但他們沒有重視,等到爆雷了才後悔莫及,塵世間最痛苦的事莫過於此。
7/19 釋出的模版例項有兩個有問題。它們中的一個因為測試系統內容驗證器(Content Validator)自身的一個臭蟲順利通過了。幸運的是,另一個有問題的例項被內容驗證器攔下了。
此時,如果負責釋出的工程師認真對待這一警報,仔細分析其原因,完全來得及把故障消滅在襁褓之中。
然而,不可思議的事發生了!
這位工程師基於以下幾點判斷,毅然決然強行釋出了更新:
-
“類似的”模版例項(其實有很大不同)從三月份起就開始用,一直沒出過問題。
-
內容驗證器包括很多檢查,它們大都通過了,除了這一個,“應該問題不大”。
-
更新配置檔案以前成功做過無數次了都沒事,“這次也不會有事”。
迷之自信,莫過於此。臨時工,一定是臨時工!

~~~~
在事故報告中,CrowdStrike 提出了一系列亡羊補牢措施。其中大多數已經在筆者上一篇文章中先行提出了,總結一下:
-
立即釋出補丁修正程式碼中的邏輯錯誤。
-
強化、完善測試案例和測試邏輯,確保每個新的模板例項在釋出前都經過完備測試。
-
分階段釋出更新:先少少地推給一部分使用者,確認沒問題再推給更多人。
-
把更新控制權交給使用者,允許他們選擇配置檔案更新的策略和時間。
-
與獨立的第三方軟體安全供應商合作,對 Falcon 感測器程式碼和端到端質量過程做進一步審查。
這些做法,不是不好,而是很好,但是還不夠好。
咋說呢?他們這些整改措施吧,雖然都對提升系統可靠性有所幫助,但思路還是聚焦在如何儘可能儘早發現程式碼中的錯誤上,寄希望於更多的測試,更完善的流程,將發生錯誤的機率降低再降低。
能不能再解放一下思想,從源頭上杜絕這類安全隱患?
打個比方,現在的做法好比做一隻木桶,要求不能漏水。他們的解決方案是買更好的放大鏡,對著成品仔細端詳,尋找每一個可能漏水的地方。同時訓練員工不能打馬虎眼。

這樣做好是好的,但是治標不治本。
更好的方法是從材質上著手,降維打擊。木頭天生就有封閉不嚴的問題,那就不用木頭好了,用鋁合金一次澆鑄成型。只要保證鋁桶有一定的厚度,那麼可以相信它是不會漏水的。
如何對安全隱患降維打擊?用形式化方法(formal methods)來開發、驗證系統,嚴格證明系統不會有此類安全問題。
具體地說,包括:
-
用記憶體安全的語言(比如 Rust)取代 C++。
-
要求程式設計師提供程式碼安全性的證明(比如透過對核心程式碼新增
標註(annotations)
),並自動檢查證明的正確性。
這些是老萬三個星期前就提過的,但 CrowdStrike 並沒有認識到它們的重要性。
那麼什麼是形式化方法?為什麼老萬對它推崇備至?
簡單地說,形式化方法就是說人是靠不住的,程式的安全性不能靠程式設計師拍胸脯,也不能光靠測試,要有靠得住的證明。
有人問:怎麼保證這個證明是正確的呢?
問得好,既然人是靠不住的,我們就不能依賴人類來驗證程式正確性的證明。驗證工作必須由機器按照一定的演算法來機械完成。機器幹別的不行,不知疲倦地執行演算法還是可以的。
有人又問了:演算法不是人設計的嗎?如何保證這個驗證證明的演算法是正確的呢?是不是還得有機器來驗證這個演算法的正確性?這不就無窮無盡了嗎?
這是很好的問題,答案是:
-
證明本身可能很複雜,但如果我們能夠把它拆成一個一個很小的步驟,每個步驟都能用機械的方法去驗證是否正確,那麼我們可以用機器來驗證這樣的證明過程。
-
只要這個驗證系統設計得好,我們可以做到,雖然寫出一個正確的證明不容易,但是檢驗證明是否正確只需要很容易的機械操作。
-
只要這個驗證系統設計得好,驗證演算法可以很簡潔。透過計算機科學家們反覆、認真的審查,我們可以事實上相信這樣的演算法沒有錯誤。所以,這個驗證過程不會無窮無盡地發散下去。
如果硬要抬槓,從理論上說很多科學家也可能會集體犯錯。但科學從來都不是保證自己絕對正確的。如果有證據某個演算法錯了,我們可以修正演算法。但只要這個驗證演算法有錯的機率足夠小,相信它還是比相信人靠譜。
Rust 為什麼比 C++ 安全?因為 Rust 把一些證明程式正確的工作強行加給了程式設計師。比如,它的型別檢查系統讓人又愛又恨。愛的是嚴格的型別檢查可以防止一大類臭蟲的發生,恨的是程式設計師經常需要很多時間才能理解編譯錯誤並找到修正辦法,讓人抓狂。對經驗不甚豐富的程式設計師,Rust 的一個編譯錯誤落在頭上就可能是一座山。這樣,在快速原型開發的時候,經常會讓人有綁沙袋行軍,用牛刀殺雞之感。
但是涉及到電腦安全這樣的事情,正是需要用牛刀的時候,就要捨得用好鋼。用 Rust 開發,代價再大能大過 150 億美元嗎?
~~~~
CrowdStrike 邀請了第三方機構來審查他們的程式碼,這是一件好事,但這件事不能只做一次,否則日後又會故態復萌。
要從根子上改變 CrowdStrike 的開發風氣,讓他們的內部程式碼審查不是走過場,而是真正起到作用。
任何一個有經驗的程式設計師,特別是有多年開發安全軟體經驗的程式設計師,對下標越界這樣的錯誤都應該非常敏感。如果看見一個數組下標操作,他們的第一反應會是:這個下標會不會越界?我能證明它永遠不會越界嗎?
這樣的反應,應該已經融化在血液裡了。如果沒有這樣的想法,只能說明他還不是一個合格的電腦安全程式設計師。
忘了檢查空指標跟忘了檢查下標越界從本質上來說都是一類問題:沒有檢查一個操作的前提條件。優秀的程式設計師對每個操作的前提和後續條件都瞭如指掌。沒有優秀的程式設計師,如何保障安全軟體自身的安全?
~~~~
再對這個安全行業多說幾句。
關於計算機安全軟體是否需要系統核心許可權這件事兒,其實並無定論。
從理論上說,要真正防住一些高階的攻擊,安全軟體必須具有核心許可權,否則自己被人乾死了還不知道。
但是對大多數普通使用者來說,這樣帶來的風險也不可小覷,甚至可能會超過被駭客攻擊的風險。7/19 就是活生生的教訓。
透過這件事情大家也看出來了,對於 CrowdStrike 這樣的電腦安全公司不必盲目崇拜。他們並不是天選之子,常人會犯的錯他們也會犯,甚至有些錯誤非常低階。
其實想想就明白,計算機安全並不是當前世界發展的熱點。大多數頭腦靈活的程式設計師都一頭扎入了網際網路、人工智慧、機器學習等來錢快的行業。這年頭還在埋頭幹軟體安全的,有的是真愛,也有的是沒有更好的選擇。
然而安全軟體的開發其實對程式設計師有更高的要求。這就出現了需求和供給的矛盾:一方面安全程式設計師的標準應該非常高,因為他們一個小小的失誤就可以讓客戶萬劫不復。另一方面,這樣的職位可能吸引不了這麼多高水平的程式設計師。
可以想象,在實踐中電腦安全公司不得不降低用人標準。這樣的後果一天半天看不出來,但出來混總是要還,只是有早有晚。
滑稽的是,千千萬萬的使用者把自身最核心的資產(資訊系統)託付給了這樣一個草臺班子,給了他們可以先斬後奏的尚方大寶劍。他們把劍揮舞得虎虎生風,塵埃落定之後,才發現自己在裸奔。
微軟說得對,安全軟體開發商需要儘量減少對核心模式的依賴,儘可能把操作移到使用者模式,否則這些安全軟體本身就是一大隱患。
但微軟自身也應該加大推進這一變革的力度,否則 Windows 系統出了問題,受損失的是微軟的客戶,大家第一時間想到的還是 Windows 不安全,久之這牌子就砸了。
具體做法可以是要求系統驅動程式的開發商用形式化的方法開發,提供其正確/安全性的可驗證的證明。
要是開發商說我的程式碼量太大,這要求太難了,微軟正好可以回應:
“沒錯,何不把你的邏輯移到使用者模式?”
~~~~
參考文獻:
-
https://fortune.com/2024/08/03/crowdstrike-outage-fortune-500-companies-5-4-billion-damages-uninsured-losses/
-
https://www.crowdstrike.com/blog/falcon-content-update-preliminary-post-incident-report/
-
https://www.crowdstrike.com/wp-content/uploads/2024/08/Channel-File-291-Incident-Root-Cause-Analysis-08.06.2024.pdf
-
https://www.microsoft.com/en-us/security/blog/2024/07/27/windows-security-best-practices-for-integrating-and-managing-security-tools/
~~~~~~~~~~
猜你會喜歡:
-
程式設計師的核心技能 – 以脫口秀的方式講解程式設計師最重要的技能
-
dongbei 語言滿月記事 – 一種基於東北方言的娛樂式程式設計語言
~~~~~~~~~~
關注老萬故事會公眾號:
本公眾號不開讚賞不放廣告。如果喜歡這篇文章,歡迎點贊、在看、轉發。謝謝大家🙏