本篇文章收錄于2021年網絡安全頂會NDSS,介紹了最新的符號執行技術,并且清晰地比較了當前流行的各種符號執行的引擎的優劣勢,可以比較系統的了解符號執行技術的相關知識
title = {{SymQEMU}: Compilation-based symbolic execution for binaries},
author = {Poeplau, Sebastian and Francillon, Aurélien},
booktitle = .NETwork and Distributed System Security Symposium},
year = 2021, organization = {Network & Distributed System Security Symposium},
month = {February}, affiliations = {Eurecom, Code Intelligence},
extralink = {Details: tools/symbolic_execution/symqemu.html}
download_address = https://www.ndss-symposium.org/wp-content/uploads/2021-118-paper.pdf`
摘 要
符號執行技術是針對軟件分析和bug檢測的強力技術,基于編譯的符號執行技術是最近提出的一種方法,當對象源代碼可以得到時可以提升符號執行的性能。本文提出了一種新的基于編譯的,針對二進制文件的符號執行技術。此系統名為symqemu,在qemu基礎之上開發,在將target程序轉換為host架構的機器碼之前修改其IR,這使得symqemu能夠將編譯符號執行引擎的能力應用于二進制文件,并且在維持架構獨立性的同時能夠獲得性能上的提升。
本文提出了這個方法以及其實現,我們利用統計學方法證明了他比最先進的符號執行引擎s2e以及qsym,在某些benchmarks上,他甚至由于針對源碼帶分析的symcc。并且利用symqemu在經過多重測試的library上發現了一個位置漏洞,證明了他在實際軟件中的可用性。
介 紹
符號執行技術近年來大力發展,一種有效但是代價大的技術,其經常與fuzzing技術混合,并成為混合fuzzing,fuzzing用來探索容易到達的路徑,而符號執行用來探索不易到達的路徑。
針對符號執行技術的重要特征之一就是其是否需要提供源代碼進行分析,而真實世界中的大多數程序(由于某些原因)是不提供源代碼的。
所以binary-only的符號執行技術被迫切需要,但是面臨一個兩難的困境,到底是選擇性能的提升還是架構的獨立性呢?比如,QSYM針對binary有很高的性能,但是其僅限于x86處理器的指令集。它不僅僅造成了系統架構依賴性,并且由于現在處理器指令的龐大提升了其復雜性。SE則是可以被廣泛的應用但是性能較差,S2E可以分析多架構代碼以及內核代碼。然而他這么做的代價是針對target程序的多種翻譯的最終表示,導致了復雜性升高以及影響性能。
在本文中,我們提出了一個方法(a)獨立于被測試的程序的target架構(b)實現復雜度低(c)具有高性能。symqemu的關鍵是qemu的cpu 仿真可以同于輕量級的符號執行機制:不是像s2e中使用中的那種計算復雜度高的將target程序向IR的轉換方式,我們hook qemu中的二進制轉換機制為了將符號處理直接編譯到機器碼中。這樣使得在性能優于最先進符號執行系統的同時可以保持加夠獨立性。目前,我們針對于linux用戶程序(ELF binaries),但我們可以將其拓展到任何qemu支持的架構中取,同時我們將symqemu開源來加速未來相關領域的研究。
將符號處理編譯到target程序中同樣是symcc的核心工作,其性能優于其他符號執行引擎,但是symcc只針對于有源碼的程序。symqemu性能優于se2以及qsym,并且相比于基于源代碼的symcc性能來說,某些情況也是可以比較。
本文工作主要有一下貢獻:
- 分析了當前最先進的binary-only的符號執行引擎并且明確了其設計中的優勢和劣勢。
- 提出了一個方法,融合了其他工具的優勢,避免了其他工具的缺點,核心idea是應用基于編譯的符號執行技術到binary上,我們工具的源代碼開源。
- 進行了詳細的評價試驗,并且實驗數據以及實驗腳本開源。
背 景
符號執行
符號執行的目的是在目標程序的執行過程中跟蹤中間值是如何計算的,每一個中間值都可以表示為程序輸入的一個公式。在任何點,系統都會使用這個公式查看這個點是否可達,這個指針是否為空等。如果答案是確定的,那么符號執行引擎將會提供測試用例,一個新的輸入例子來觸發對應的行為。所以符號執行可以被方便的用來探測程序路徑以及觸發bug。
為了跟蹤程序的計算過程,符號執行系統必須對程序指令集有一個深入的理解,許多實現都是通過將程序翻譯為IR,比如LLVM和VEX。IR隨后被符號化執行,因為執行器只需要處理IR(通常由少量的指令集構成),所以實現相對比較簡單。<font color=red>并且在之前的工作中我們發現,在對進行測試的程序的高級表示的查詢較低級指令的表示的查詢更加容易解決。</font>
然而將程序轉換為IR需要計算能力并且對程序執行過程引入了開銷;然而一些實現過程放棄了翻譯而直接工作在機器代碼上,這種解決方案除了性能上的優勢,同時在執行器無法怎樣解釋指令時,會幫助提升魯棒性。然而在另一方面,這種解決方案會導致執行器被限制在了一種特定的架構之中。另一種基于源碼的執行器在實際中并不是那么廣泛使用,因為大多數情況下只能得到二進制文件。
binary-only符號執行
僅僅針對二進制文件的符號執行添加了許多挑戰:缺少源代碼,將程序翻譯為IR需要可靠的反匯編器;由于靜態反匯編的挑戰,大多數實現都是在運行態按需進行反匯編。當源碼不可得時,針對架構的支持同樣也是重要的,此時交叉編譯不可行。尤其針對嵌入式設備來說,缺少對多架構的支持是不可行的。
無需翻譯的執行器除了面對復雜實現帶來的可維護問題外,還面臨可移植性問題。將程序翻譯為中間語言的執行器需要可靠的反匯編器,已經有大量的工作來確定翻譯器的準確性。基于源碼的執行器可以較容易的獲得IR。
基于二進制文件的符號執行對于高性能以及多架構支持具有更迫切的需求。
最先進解決方案
下面描述最先進的符號執行實驗方案以及他們各自對應解決的問題。
angr

一個經典的符號執行翻譯器,使用VEX,Valgrind框架的翻譯器和IR。目標程序在運行時被翻譯。其中一個優化,angr可以在Unicorn,基于qemu的快速CPU模擬器,上執行不涉及符號數據的計算。
由于基于VEX,agnr固然可以支持所有VEX能夠處理的架構,因為angr核心由Python/ target=_blank class=infotextkey>Python語言實現,所以他速度慢但是很通用。
s2e

由于想要將基于源代碼符號執行覆蓋范圍拓展到目標程序依賴以及操作系統內核,創造了s2e。為了實現這個目的,s2e在qemu仿真器內執行整個操作系統并且將其與KLEE鏈接為了符號化執行相關代碼。
這個系統相當復雜,包括被測試程序的多重翻譯:
- QEMU是一個二進制文件翻譯器,比如在通常操作中,他講目標程序從機器代碼翻譯為一種中間表示即TCG ops,然后將其重新編譯為針對host CPU的機器碼。
- 當執行是設計符號化數據時,S2E使用的修改過的QEEMU不再將TCGops重編譯為機器代碼,他將其翻譯為LLVM bitcode,隨后將其傳遞給KLEE。
- KLEE符號化解釋執行LLVM bitcode,然后將結果的具體情況回傳給QEMU。
此系統可以很靈活的處理不同處理器架構,并且可以支持針對操作系統全層面的計算跟蹤。然而他需要付出一下代價:S2E是一個具有龐大代碼基礎的復雜系統。并且兩部分翻譯,從機器碼翻譯為TCG ops和從TCG ops翻譯為LLVM bitcode損害了他的性能。與angr針對用戶態程序來比較,S2E需要更多的設計建立以及運行,但是提供了一個更加全面的分析。
QSYM

QSYM在性能上有極大的增強,他不將目標程序翻譯為中間語言。他在運行態時向x86機器碼內進行插樁來向二進制文件內添加符號追蹤。具體來講,他應用了Inter Pin,一種動態二進制插樁框架,來向目標程序內插入hook代碼。在hook內部,他和程序運行的實際代碼等價的運行符號代碼。
這種方式產生了一種針對x86程序的非常快速并且魯棒性很強的符號執行引擎。然而,這個系統固然會被限制在x86框架內,并且實現是繁瑣的,因為他需要處理在計算中可能出現的任何指令。并且將其遷移到其他架構將會有很大的困難。
symcc

最近提出的符號執行工具,SYMCC,同樣是本文作者之前的工作,基于源代碼的,不支持分析二進制文件。SYMQEMU的靈感來自于SYMCC,所以簡要概括一下他的設計。
我們在設計SYMCC時觀察到,目前大多數符號執行系統是解釋器。然而我們卻提出一個基于編譯的方法,并且展示了他能夠提升執行性能以及系統實際探索能力。SYMCC在編譯器內進行hook,并且在target代碼內進行插裝,并且注入實施支持庫的調用。因此符號執行成為了被編譯文件的一部分。并且分析代碼可以進行優化,并且插裝代碼并不會在每次執行時進行重復。
SYMCC基于編譯的方式需要編譯器,所以他只能在被測試程序源代碼可用時發揮作用。盡管如此,我們認為這個方式是足夠有前途,所以一直尋找一種方式將其應用到binary-only的方面之中,本文的主要工作就是說明基于編譯的符號執行系統是如何在二進制文件上高效的工作。
SYMQEMU
現在提出針對binry-only設計實現的SYMQEMU。他的靈感來自于之前的工作并結合了如今最先進的符號執行系統的技術。
design
系統兩個主要目標:
- 實現高效能,以致于實際軟件。
- 合理的架構獨立性,比如將其遷移到新的處理器架構不需要做過多工作。
基于之前的調查,我們發現流行的最先進的符號執行系統實現了如下兩個目標中的一個,但并非全部:angr和s2e是架構靈活的但是性能差;QSYM在性能上比較高但是其只針對x86架構。
如今針對架構獨立的解決方案是將被測程序翻譯為IR,這樣如果想要支持一個新的架構,只有翻譯器需要移植,理想情況下,我們選擇一種中間語言,其已經存在支持多種架構的相關翻譯器。以中間語言靈活地表示程序是一種著名的,已經成功的應用于許多其他領域的技術,比如編譯器設計以及靜態代碼分析。我們也將這種技術合并到我們的設計中來。
當將程序翻譯為中間語言獲得便利的同時,我們同樣需要了解這種方式對于性能的影響:將binary-only程序靜態翻譯是具有挑戰性的,因為反匯編器可能是不可靠的,尤其是存在間接跳轉的情況下,并且在分析過程中運行時進行翻譯會提升功耗。我們認為這是s2e性能劣于QSYM的主要原因。我們的目標就是找到一種翻譯系統同時保持性能優越。
首先,我們主要到s2e以及angr都收到了非重要問題的影響,并且這些問題都是可以通過工程方面的工作解決的:
- S2E將被測試程序翻譯了兩次,然而如果符號執行過程是在第一次中間表示上實現的話,第二次翻譯過程其實是可以避免的。
- angr的性能受到python實現影響,將其核心代碼移植到一種更快速的語言中會顯著提升速度。
然而我們的貢獻并不僅僅是找出并且避免上述兩個問題,我們還觀測到:s2e以及angr,以及其他所有的binaty-only的符號執行器,都解釋執行被測試程序的中間表示,解釋是設計的核心部分。我們推測,將目標程序編譯為插樁版本將會帶來很高的性能上的提升。雖然SYMCC是基于源代碼的,基于編譯的符合執行引擎,但是他證明了這一點。
收到上述觀測到的啟發,我們的設計方法如下:
- 在運行態將目標程序翻譯為IR。
- 為符號執行所需的IR插樁。
- 將IR編譯為適合CPU運行分析的機器碼并且直接執行。
通過將插樁的目標程序編譯為機器碼,補償了在第一階段將二進制文件翻譯為中間代碼時的性能損失。CPU執行機器碼比解釋器運行IR速度快得多,因此我們獲得了一個可以與沒有翻譯的系統的性能相當的系統,同時由于進行了程序翻譯可以保持架構的獨立性。
implementation

我們在qemu的基礎之上實現了SYMQEMU,選擇qemu的原因是因為他是一個魯棒性的系統仿真器,并且可以支持許多架構,在他的基礎之上進行實現,我們可以實現架構獨立性。并且qemu還有另一個特點正好滿足我們的需求,他不僅將二進制文件翻譯為針對處理器獨立的IR,他同時支持將中間語言便已成為host CPU的機器碼。qemu的主要優點是他能夠將二進制文件翻譯為不同host架構的機器代碼,并且可以完成全系統仿真,方便于之后拓展支持交叉架構的固件分析。

具體來說,我們拓展了QEMU的組件TCG。在未被修改的qemu中,TCG負責將guest架構的機器碼塊翻譯為架構獨立的語言,叫做TCG ops,然后編譯這些TCG ops為host架構的機器碼。由于性能原因,這些翻譯好的blocks隨后被緩存,所以翻譯在每次執行過程中只需要進行一次。SYMQEMU在這過程中插入了一步:當被測程序翻譯為TCG ops時,我們不僅插樁來模擬guest CPU而且產生一些額外的TCG ops來建立對應的符號約束表達式。針對建立符號表達式以及求解這些的支持庫,symqemu重用SYMCC的支持庫,即重用QSYM的。
(此處有詳細例子,感興趣去讀原文)
目前我們使用的qemu linux用戶模式的仿真,即我們只模擬了普通用戶空間的guest系統。系統調用被轉換來滿足host架構的要求,這些是針對host的內核來工作的,使用了qemu常規的機制。因此我們的符號執行分析在系統調用處停止,與QSYM以及angr類似。與全系統仿真(比如s2e)來講,這樣節省了為每個target架構準備系統鏡像的方面,并且提升了性能,因為是直接運行kernel代碼而不是通過仿真。但是如果需要的話,SYMQEMU是很容易的被拓展為QEMU的全系統仿真。
架構 獨立
首先要明確,執行分析的主機的架構叫做host,被測代碼在其架構之上被編譯的叫做guest。尤其是在嵌入式設備分析中,host與guest架構不同是顯然的,嵌入式設備的系統進行符號執行分析的能力不足,所以將固件放置到其他系統中進行分析,SYMQEMU就是為這種情況準備的,能夠在多架構下運行。
SYMQEMU利用qemu TCG translators,涵蓋多種處理器類型,并且我們針對其修改幾乎獨立于target架構。
也就是說,SYMQEMU可以在相關的host架構上運行并且可以支持所有qemu能夠處理的guest架構下的二進制文件的分析。
與之前的設計比較

本節之處SYMQEMU與最先進的符號執行系統的不同之處。
與angr和s2e相似,SYMQEMU使用傳統的,以IR來完成符號執行處理,顯著的降低了實現的復雜性。但是不同于此二者的是,他是基于編譯的符號執行技術,顯著的提升了性能。
與QSYM比較,SYMQEMU設計最重要的優勢是架構靈活性的同時,能夠維持很高的執行速度。在qemu之上進行設計使其能夠或者很多的數量的模擬器支持的架構處理能力。
SYMCC雖然不能夠分析二進制代碼,但是其給SYMQEMU提供了基于編譯的思路。此二者都是通過修改其IR來在目標程序中插入符號處理,并且都是將結果直接編譯為能夠高效運行的機器碼。然而SYMCC是面向源代碼的,而SYMQEMU解決了分析二進制文件的不同指令集的挑戰,SYMQEMU在翻譯過程中的TCG ops中插樁,SYMCC在編程過程中的LLVM bitcode內插樁。并且SYMQEMU解決了guest和target架構不匹配的問題。
我們認為本文工作結合了s2e以及angr的優勢,即多架構支持,同時結合了symcc的優點,高性能,摒棄了他們的缺點;并且我們找到了一種方式,能夠將SYMCC的核心idea應用到二進制文件的分析之中。
內存管理
當symqemu分析軟件時,他會建立很多符號公式來描述中間結果和路徑約束。他們占用的內存會隨著時間而一直增長,所以symqemu需要一種方式來清除那些不再被使用的公式。
首先我們討論一下為什么內存管理是第一位的。IR在任何合理的程序中或對程序流有影響,或者成為最終結果的一部分;在前者情況下,對應的表達式被添加到路徑約束的集合中,并且不能被清楚;但是針對后者情況,表達式成為最終結果的描述中的字表達式。所以符號表達式是什么時候變成不重要的呢?關鍵就是程序的輸出是程序結果的一部分,但是他可能在程序的結束之前就已經產生了。
所以我們應該在符號在最后一次使用之后將其清除。QSYM使用的C++ smart points來實現了這個目的,但是我們在被修改的qemu中不能簡單的相同的辦法:TCG是一個動態翻譯器,由于性能因素,它不產生任何被翻譯代碼的拓展分析。這使得高效的確定插入清除代碼的位置非常困難。并且經驗告訴我們,大多數程序中包含很少的,在程序執行過程之中無用的,相關符號數據和表達式,所以我們不想我們的清除機制造成很大的功耗。
我們使用了一種樂觀的清除機制,在一種expression garbage collector的基礎之上:SYMQEMU跟蹤所有從后端獲得的符號表達式,如果他們的數目非常大時進行回收。最主要的觀測是所有live表達式可以通過掃描如下發現
- 模擬的CPU符號寄存器
- 存儲對應符號內存結果的符號表達式的,內存中的shadow regions
以上兩種,后端都是可感知的。在感知到所有live表達式之后,symqemu將其與所有已經創建的符號表達式進行比較,并且釋放那些不再使用的表達式。尤其是當一個程序在寄存器和內存中移除了計算的結果,對應的符號表達式同樣被認為不再使用也被移除。我們將 expression garbage collector 和QSYM’s smart pointer based memory management相聯系,這兩種基礎都認為表達式不再使用之后可以被釋放。
修改TCG ops
我們的方法要求能夠像TCG ops中插樁。然而TCG不支持在翻譯過程之中的拓展修改功能,作為一個翻譯器,他高度關注速度問題。因為,對于TCG ops的程序化修改的工作很少。然而LLVM提供了豐富的API,支持compiler檢查和修改LLVM bitcode。TCG ops單純的將指令存儲在一個平面鏈表中,而沒有任何高層次的類似于基本快的數據結構。并且程序流被期望與翻譯塊呈線性關系。
為了不和TCG產生不一致,我們的實現對每一個指令生成時進行符號處理。雖然這種方法可以避免與TCG 優化以及代碼生成器產生的問題,但是使得靜態優化技術不可行,因為我們每次僅僅關注一條指令。尤其是我們無法靜態確定給定的值是否是實際值,并且如果所有的操作都是符號值的情況下,我們也不能產生跳過符號計算的階段的跳轉。
因此我們最終于TCG所需要的運行環境的限制條件達成了妥協,同時允許我們有相關很高的執行速度:我們在支持的調用庫中進行實際值性檢查,這樣,如果實際計算的輸入都是準確值的話,就可以直接跳過符號值計算,但是這樣會導致額外的庫調用開銷。
shadow call stack
QSYM引入了上下文敏感的基本快剪枝,如果在同樣的調用堆棧環境中頻繁調用確定的計算會導致壓抑符號執行(基于如下直覺,在同樣的上下文環境中重復的執行分析并不會導致新的發現)。為了實現這個優化,符號執行需要維護一個shadow call stack,記錄跟蹤call以及return指令。
在qemu基礎之上,我們面臨一個新的挑戰,TCG ops是一個非常低級別的target程序的中間表示。尤其是,call以及return指令不是被表示為單獨的指令而是被翻譯為一系列TCG ops。比如一個在x86架構下的程序調用會生成TCG ops,其將返回地址push到模擬的stack上,調整guest的stack pointer,并且根據被調用函數來修改guest的指令。這使得僅僅通過檢測TCG ops來以一個可靠并且架構獨立的方式來識別call以及return是不可能的。我們選擇了如下優化來提高魯棒性:在架構獨立的,能夠將機器代碼轉換為TCG ops的qemu 代碼中,每當遇到call和return時,我們會通知代碼生成器。缺點就是針對每個target架構,類似的通知代碼都必須被插入到翻譯代碼中去,但這并不復雜。
評 價
詳見原文,主要是一些指標與測試效果
未來工作
全系統仿真
SYMQEMU目前運行符號執行針對linux用戶態二進制程序,之后將會對其拓展到全系統分析,尤其是針對嵌入式設備而言,分析此類程序要求全系統仿真。
我們認為在SYMQEMU實現這一改進是可能的。將target翻譯為TCG ops,對其插樁,并將其編譯為機器碼,這些基本過程不改變。需要添加的一個機制是將符號化數據引入到guest系統中,這是受到S2E fake-instruction技術的啟發,以及當在guest內存以及符號表達式之間存在映射時,shadow-memory系統需要記錄虛擬MMU的數量。最終將會產生一個不僅可以對用戶態程序進行測試,同樣可以對內核代碼進行測試的系統,并且其同樣可分析非linux系統的代碼以及裸固件等。
caching across executions
混合fuzzing技術的特點之一是能夠對同一程序進行大量的連續執行。作為動態翻譯器,SYMQEMU在運行態按需翻譯target程序。并且翻譯的結果在單個運行的過程之中被緩存,但是當目標程序執行終止時這些緩存結果會被丟棄。我們猜想,可以通過緩存多個執行過程中的翻譯結果,可以顯著提升結合SYMQEMU的混合FUZZ的性能。主要的挑戰就是需要確定目標是確定性加載的,以及針對自我修改代碼需要進行特殊處理。所以,這些潛在的優化性能提升主要在于被測程序的特點。
symbolic QEMU helpers
QEMU利用TCG ops表示機器碼,然而一些target的指令難以用TCG ops來進行表示,尤其是在CISC架構之上。針對這情況,QEMU使用helpers:可以被TCG調用的內置變異函數,仿真target架構的每一個復雜指令。由于helpers工作在常規的TCG架構之外,SYMQEMU在TCG層級的插樁不能插入符號處理到他們之中。這樣的結果是implicit concretization,在分析使用大量目標的指令時會產生精讀損失。
我們有如下兩種實現qemu helpers符號處理的方式:
- 第一種方式是為每一個要求的helper手動添加符號等價式,更像在一些符號執行引擎中使用的常用libc功能的函數總結。這個方式非常容易實現,但是不方便應用于大數量的helpers中。
- 另一種方式是自動化的實現helpers的符號化版本。為了實現這個目的,SYMCC可以被用來編譯符號化追蹤到helpers中,他的源代碼作為QEMU的一部分是公開的。最終得到的二進制文件是和SYMQEMU兼容的,因為SYMCC的使用相同的符號推理的后端。S2E也是使用類似的方式編譯helpers到KLEE中的解釋器中的LLVM bitcode。
相關工作
binary-only符號執行
Mayhem是一個高性能的基于解釋器的符號執行系統,贏得過DAPRA CGC比賽,然而由于其不開源無法比較性能。Triton是可以以兩種方式運行的符號執行系統,一種使用二進制文件轉換,類似于QSYM,一種使用CPU仿真,類似于S2E以及angr。Eclipser覆蓋了介于fuzzing和符號執行之間的一些中間區域,他認為在分支條件和輸入數據之間存在線性關系。這種約束的簡化提升了系統的性能,然而他卻不能發現常規符號執行系統可以發現的那些路徑。Redqueen利用一種啟發式的方法尋找路徑條件和輸入之間的關系。SYMQEMU相比較來說實現了全系統仿真。
運行態bug檢測
混合fuzzing依靠fuzzer以及sanitizers來檢測bugs。Address sanitizer是一種流行的用來檢測確定內存錯誤的sanitizer。由于其需要源代碼來產生插樁程序, Fioraldi et al設計了QASan,基于qemu的系統來對二進制文件實現類似的檢測。有大量的需要源代碼的sanitizers。我們推測通過QASan的思路,可以將大量上述sanitizers用于二進制文件分析。
混合fuzzing
Driller是基于angr的混合fuzzer,其設計理念類似于QSYM,但是有其angr的python實現以及基于解釋器的方式,其執行速度較低。與QSYM以及SYMQEMU比較,它使用了一種更加精細的策略來融合fuzzer以及符號引擎:他監控fuzzer的進展情況,并且當其似乎遇到自身無法解決的障礙時,會自動切換到符號執行。類似的,最近提出的Pangolin通過不僅提供fuzzer測試用例,以及一些抽象的符號約束,還有快速樣本生成方法,強調了fuzzer結合符號執行的優勢;利用這些,fuzzer能夠生成可以有很大概率解決由符號執行生成的路徑約束的輸入。
我們認為更加精細的符號執行和fuzzer的組合可以很大程度上提升混合fuzzing的性能
總 結
我們提出了SYMQEMU,一種基于編譯的,針對二進制文件的符號執行引擎。我們的評價展示了SYMQEMU性能優于最先進的符號執行引擎并且可以在某些方面與基于源代碼的符號執行技術相匹配。而且SYMQEMU非常方便的向其他架構進行遷移,只需要幾行代碼即可。