测试页打印失败.是否参阅打印疑难解答以获得帮助_使用DeepState对API进行模糊测试(上)...
前言
DeepState是一個框架,它為C和c++開發人員提供了一個公共接口,用于各種符號執行和模糊引擎。用戶可以使用類似于Google Test的API編寫一個測試工具,然后使用多個后端執行它,而不必了解底層引擎的復雜性。它支持編寫單元測試和API序列測試,以及自動測試生成。
使用DeepState,我們就可以編寫一個紅黑樹(red-black tree)模糊器。之后只需付出較少的努力,就可以將其變成一個功能更加全面的測試生成器。 盡管DeepState模糊器不需要很多的編碼工作,但它支持回歸測試(regressiontest)的重放,以減少用于調試的測試用例的大小,以及多個數據生成后端,包括Manticore,angr,libFuzzer和AFL。使用符號執行,我們甚至發現了原始模糊忽略的人為引入的漏洞。
在此,我保證你閱讀完本文后,可以完全在自己的API中進行一次高質量的自動化測試。
背景信息
2013年,猶他州大學計算機系的John Regehr撰寫了一篇關于“如何實現ADT的模糊測試”的博客。John詳細地討論了一些一般性問題,比如代碼覆蓋率、測試代碼和差異測試。如果你尚未閱讀John的文章,那么我建議你現在就閱讀。
一般性的模糊測試很簡單,只需要有一個測試軟件,利用John的方法就可以在測試對象上提供一組函數或方法。我們在這篇文章中的運行示例是一個紅黑樹,不過,AVL樹、文件系統、內存存儲,甚至密碼庫都可以達到類似的測試目的。由于我們的目標是對軟件進行徹底的測試,而傳統的單元測試方法則是編寫一系列看起來像以下這樣的小函數。
result1 = foo(3, "hello");
result2 = bar(result1, "goodbye")
assert(result2 == DONE);
也就是說,每個測試都會按著這樣的步驟來進行:“先運行一些測試內容,然后檢查它是否和預期的是一樣的。”這種方法有兩個問題。首先,工作量很大。其次,這項工作的測試效果并不像你想象的那樣好,每個測試都只針對一個測試內容,如果測試的人沒有提前把潛在的問題考慮進去,那么測試過程也就會忽略這些問題。
而在模糊測試中,用隨機壞數據(也稱做 fuzz)攻擊一個程序,然后等著觀察哪里遭到了破壞。模糊測試的技巧在于,它是不符合邏輯的:自動模糊測試不去猜測哪個數據會導致破壞(就像人工測試員那樣),而是將盡可能多的雜亂數據投入程序中。由這個測試驗證過的失敗模式通常對程序員來說是個徹底的震撼,因為任何按邏輯思考的人都不會想到這種失敗。模糊測試是一項簡單的技術,但它卻能揭示出程序中的重要漏洞。它能夠驗證出現實世界中的錯誤模式并在您的軟件發貨前對潛在的應當被堵塞的攻擊渠道進行提示。
模糊測試通常被認為會生成文件或數據包,但它也可以生成測試軟件庫的API調用序列。
foo_result = NULL;
bar_result = NULL;
repeat LENGTH times:
? ?switch (choice):
? ? ? choose_foo:
? ? ? ? ?foo_result = foo(randomInt(), randomString());
? ? ? ? ?break;
? ? ? choose_bar:
? ? ? ? ?bar_result = bar(foo_result, randomString());
? ? ? ? ?break;
? ? ? choose_baz:
? ? ? ? ?baz_result = baz(foo_result, bar_result);
? ? ? ? ?break;
? ?checkInvariants();
也就是說,fuzzer先反復選擇要調用的隨機函數,然后調用所選擇的函數,測試期間可能會存儲結果,以便在以后的函數調用中使用。
John的帖子不僅提供一般性建議,還包含了紅黑樹的工作模糊器的鏈接。這個模糊器是有效的,它是一個很好的例子,說明了如何使用基于隨機值生成的可靠測試工具來真正測試API。但是,它也不是一個完全實用的測試工具。它雖然會生成輸入,并測試紅黑樹,但當模糊器發現漏洞時,它只會打印一個漏洞信息隨即便崩潰。你沒有學到任何東西,除了“你的代碼有一個漏洞”這樣的信息外,我們再也得不到任何有價值的信息。
理想情況下,模糊器會自動將失敗的測試序列存儲在一個文件中,最小化這些序列以簡化調試,并且可以在回歸套件中重放過去的失敗測試。編寫支持所有這些基礎設施的代碼很是枯燥(尤其是在C/ c++中),而且會顯著增加測試工作所需的工作量。另外處理類似捕獲斷言違規和硬崩潰的現象,以便在終止之前將測試編寫到文件系統,也很難做到。
AFL和其他常用的模糊器通常會提供這種功能,這使得模糊測試成為一種更實用的調試工具。不幸的是,這種模糊測試器并不便于測試API。它們通常會生成一個文件或字節緩沖區,并期望被測試的程序將該文件作為輸入。將一系列字節轉換為紅黑樹測試可能比重新編寫更有趣,但工作量也很大。你真正想要的是一個像GoogleTest這樣的單元測試框架,但它能夠改變測試中使用的輸入值。有很多很好的隨機測試工具,包括TSTL,但很少有復雜的工具以C / C ++為目標,而且據我所知,沒有一個工具允許你使用除了工具內置的隨機測試器之外的任何測試生成方法。這就是我們想要的GoogleTest,它能夠使用libFuzzer,AFL,HonggFuzz或生成數據。
?DeepState
DeepState滿足了我以上提到的所有測試要求,甚至更多。將John的模糊器轉換為DeepState測試工具相對容易,可以在文件deepstate_harness.cpp中找到DeepState的主要更改項,總共5項:
1.刪除main并用一個已經命名的test (test (RBTree, GeneralFuzzer))替換它;
1.1DeepState文件可以包含多個名為test的測試,但是只有一個測試是可以用的;
2.只需在每個測試中創建一個樹,而不是使用一個外部循環來迭代每次影響單個樹的調用:
2.1與模糊測試循環不同,我的測試更接近于非常一般化的單元測試,即每個測試都會執行一個有趣的API調用序列;
2.2DeepState將處理運行多個測試,模糊或符號執行引擎將提供“外部循環”;
3.將每個API調用序列的長度固定為固定值,而不是隨機值。
3.1文件頂部的#define LENGTH 100控制我們在每個測試中調用的函數數量;
3.2在每次測試中,字節都在相同的位置,這對基于突變的模糊器很有幫助。注意:極長的測試將超出libFuzzer的默認字節長度。
3.3只要它們不消耗那么多字節使得模糊器或DeepState達到它們的極限,或者找不到要變異的正確字節,更長的測試通常比更短的測試更好。可能有一個長度為5的序列暴露了你的漏洞,但是DeepState的暴力模糊器甚至libFuzzer和AFL可能很難找到它,并且更容易生成相同問題的長度為45的版本。另一方面,符號執行可以找到任意長度的罕見序列。
3.4為簡單起見,我在我的工具中使用了#define,但是也可以將這些測試參數定義為帶有默認值的可選命令行參數,以便在測試中獲得更大的靈活性。只需使用與DeepState相同的工具來定義自己的命令行選項(請參閱DeepState.c和DeepState.h)。
4用DeepState_Int()、DeepState_Char()和DeepState_IntInRange(…)調用替換各種rand() % NNN調用。
4.1 DeepState提供調用來生成你想要的大多數基本數據類型,可以選擇在受限制的范圍內生成。
4.2實際上你可以使用rand()代替DeepState調用,如果你的調用包含DeepState并已定義DEEPSTATE_TAKEOVER_RAND,則所有rand調用都將轉換為適當的DeepState函數。 easy_deepstate_fuzzer.cpp文件顯示了它的工作原理,但它并不理想,因為它不提供任何記錄來顯示測試期間發生的情況。這通常是將現有模糊器轉換為使用DeepState的最簡單方法。注意:John的模糊器的變化很小,90%的工作只是更改了一些include和刪除main。
5. 用DeepState的OneOf結構替換選擇要進行API調用的switch語句:
5.1 OneOf獲取C ++ lambda的列表,并選擇一個執行;
5.2此更改并不是必須的,但使用OneOf可簡化代碼并允許優化選擇和減少智能測試工作量;
5.3另一個版本的OneOf接受一個固定大小的數組作為輸入,并返回一些值,例如,OneOf(“abcd”)將產生一個字符a,b,c或d。
除此之外,還有許多其他的修飾性(例如格式化,變量命名)更改,但底線就是確保模糊器的本質屬性不被破壞。通過這些更改,模糊器除了不運行fuzz_rb可執行文件,幾乎和以前一樣。我將使用DeepState運行我們定義的測試并生成輸入值,選擇要進行的函數調用,什么值插入在紅黑樹,以及由DeepState_Int,OneOf和其他調用表示的所有其他決策:
int GetValue() {
? if (!restrictValues) {
? ? return DeepState_Int();
? } else {
? ? return DeepState_IntInRange(0, valueRange);
? }
}
...
? for (int n = 0; n < LENGTH; n++) {
? ? OneOf(
? ? ? [&] {
? ? ? ? int key = GetValue();
? ? ? ? int* ip = (int*)malloc(sizeof(int));
? ? ? ? *ip = key;
? ? ? ? if (!noDuplicates || !containerFind(*ip)) {
? ? ? ? ? void* vp = voidP();
? ? ? ? ? LOG(TRACE) << n << ": INSERT:" << *ip << " " << vp;
? ? ? ? ? RBTreeInsert(tree, ip, vp);
? ? ? ? ? containerInsert(*ip, vp);
? ? ? ? } else {
? ? ? ? ? LOG(TRACE) << n << ": AVOIDING DUPLICATE INSERT:" << *ip;
? ? ? ? ? free(ip);
? ? ? ? }
? ? ? },
? ? ? [&] {
? ? ? ? int key = GetValue();
? ? ? ? LOG(TRACE) << n << ": FIND:" << key;
? ? ? ? if ((node = RBExactQuery(tree, &key))) {
? ? ? ? ? ASSERT(containerFind(key)) << "Expected to find " << key;
? ? ? ? } else {
? ? ? ? ? ASSERT(!containerFind(key)) << "Expected not to find " << key;
? ? ? ? }
? ? ? },
...
DeepState的安裝
DeepState GitHub存儲庫提供了很多詳細信息和依賴項,但在我的MacBook Pro上,安裝很簡單。
git clone https://github.com/trailofbits/deepstate
cd deepstate
mkdir build
cd build
cmake ..
sudo make install
構建啟用了libFuzzer的版本稍微復雜一些:
brew install llvm@7
git clone https://github.com/trailofbits/deepstate
cd deepstate
mkdir build
cd build
CC=/usr/local/opt/llvm\@7/bin/clang CXX=/usr/local/opt/llvm\@7/bin/clang++ BUILD_LIBFUZZER=TRUE cmake ..
sudo make install
AFL也可用于為DeepState生成輸入,但大多數情況下,原始速度(由于不需要分叉),比較分解和值配置文件似乎為libFuzzer提供了這種API測試的優勢。有關在DeepState中使用AFL和其他基于文件的模糊器的更多信息,請參閱DeepState文件。
使用DeepState紅黑樹模糊器
安裝DeepState后,構建紅黑樹模糊器的過程也就很簡單了。
git clone https://github.com/agroce/rb_tree_demo
cd rb_tree_demo
make
make命令使用了我們能想到的所有殺毒軟件(address、undefined和integer)編譯所有內容,以便在模糊處理中捕獲更多漏洞。雖然這樣做會降低運行性能,但卻很有價值。
如果你使用macOS并使用非Apple clang來獲得libFuzzer支持,那么你需要做以下的事情。
CC=/usr/local/opt/llvm\@7/bin/clang CXX=/usr/local/opt/llvm\@7/bin/clang++ make
這樣,你就能使用到正確的編譯器版本,例如,homebrew-installed。
這將為你提供一些不同的可執行程序,一個是fuzz_rb,它只是John使用的模糊器,使用時長只有60秒,ds_rb可執行文件是DeepState可執行文件。你可以用一個簡單的BF算法模糊器來模糊紅黑樹。
mkdir tests
./ds_rb --fuzz --timeout 60 --output_test_dir tests
如果要查看有關fuzzer正在執行的操作的更多信息,可以使用--log_level指定日志級別,以明確要查看的信息的重要性。 log_level為0,則代表包括所有信息,甚至包括調試信息,log_level為1時,代表的是來自被測系統的TRACE信息(例如,由上面顯示的LOG(TRACE)代碼產生的信息)。log_level為2時,代表的是來自DeepState本身的INFO,非關鍵信息(這是默認的,通常是合適的), log_level為3時,代表的是警告信息,依此類推。在模糊測試結束時,測試目錄應為空,因為repo中的紅黑樹代碼是沒有漏洞的。如果將--fuzz_save_passing添加到選項中,你將在目錄中得到大量用于傳遞測試的文件。
最后,我們可以使用libFuzzer生成測試。
mkdir corpus
./ds_rb_lf corpus -use_value_profile=1 -detect_leaks=0 -max_total_time=60
ds_rb_lf可執行文件是普通的libFuzzer可執行文件,具有與libFuzzer相同的命令行選項。它將運行libFuzzer 60秒,并將任何有趣的輸入(包括測試失敗)放在語料庫目錄中。如果發生崩潰,它將在當前目錄中留下崩潰的文件。你可以通過確定測試使用的最大輸入大小來調整它,以在某些情況下執行得更好,但這是一項非常重要的練習。在長度為100的情況下,最大大小和4096字節之間的差距不是非常大。
對于更復雜的代碼,像libFuzzer或AFL這樣的基于覆蓋的且基于工具的模糊器將比John的模糊器或簡單的DeepState模糊器的隨機性更強。對于像紅黑樹這樣的例子,這可能沒有那么重要。隨機性更強的模糊器也具有產生測試語料庫的優勢,這些測試可以產生有趣的代碼覆蓋。 DeepState允許你使用更快的模糊器進行快速運行,并使用更智能的工具進行更深入的測試。
我可以輕松地重放任何DeepState生成的測試(來自libFuzzer或DeepState的模糊測試器):
./ds_rb --input_test_file file
或者重放整個測試目錄:
./ds_rb --input_test_files_dir dir
在重放整個目錄時添加--exit_on_fail標志,可讓你在遇到失敗或崩潰測試時立即停止測試。這種方法可以很容易的用于將DeepState中發現的失敗添加到自動回歸測試中。
認為的添加一個漏洞
雖然一切都很順利,但它沒有讓我對John的模糊或DeepState充滿信心。即使我們更改了Makefile來查看代碼覆蓋率,也很容易編寫一個不檢查正確行為的模糊器。也就是說模糊測試涵蓋了所有內容,除了出現崩潰之外沒有發現任何漏洞。要查看正在運行的模糊器,以及DeepState提供給我們的更多信息,我可以主動添加一個漏洞。轉到red_black_tree.c的第267行,將1改為0。新文件和原始文件的差異應該如下所示:
267c267
< ? x->parent->parent->red=0;
---
> ? x->parent->parent->red=1;
使用新的red_black_tree.c重建所有模糊器后,運行John的模糊器會立即引發崩潰。
time ./fuzz_rb
Assertion failed: (left_black_cnt == right_black_cnt), function checkRepHelper, file red_black_tree.c, line 702.
Abort trap: 6
real 0m0.100s
user 0m0.008s
sys 0m0.070s
使用DeepState模糊器可以快速生成模糊結果:
time ./ds_rb --fuzz --log_level 1 --exit_on_fail --output_test_dir tests
INFO: Starting fuzzing
WARNING: No seed provided; using 1546625762
WARNING: No test specified, defaulting to last test defined (RBTree_GeneralFuzzer)
TRACE: Running: RBTree_GeneralFuzzer from deepstate_harness.cpp(78)
TRACE: deepstate_harness.cpp(122): 0: DELETE:-747598508
TRACE: deepstate_harness.cpp(190): checkRep...
TRACE: deepstate_harness.cpp(192): RBTreeVerify...
TRACE: deepstate_harness.cpp(122): 1: DELETE:831257296
TRACE: deepstate_harness.cpp(190): checkRep...
TRACE: deepstate_harness.cpp(192): RBTreeVerify...
TRACE: deepstate_harness.cpp(134): 2: PRED:1291220586
TRACE: deepstate_harness.cpp(190): checkRep...
TRACE: deepstate_harness.cpp(192): RBTreeVerify...
TRACE: deepstate_harness.cpp(190): checkRep...
TRACE: deepstate_harness.cpp(192): RBTreeVerify...
TRACE: deepstate_harness.cpp(154): 4: SUCC:-1845067087
TRACE: deepstate_harness.cpp(190): checkRep...
TRACE: deepstate_harness.cpp(192): RBTreeVerify...
TRACE: deepstate_harness.cpp(190): checkRep...
TRACE: deepstate_harness.cpp(192): RBTreeVerify...
TRACE: deepstate_harness.cpp(113): 6: FIND:-427918646
TRACE: deepstate_harness.cpp(190): checkRep...
...
TRACE: deepstate_harness.cpp(192): RBTreeVerify...
TRACE: deepstate_harness.cpp(103): 44: INSERT:-1835066397 0x00000000ffffff9c
TRACE: deepstate_harness.cpp(190): checkRep...
TRACE: deepstate_harness.cpp(192): RBTreeVerify...
TRACE: deepstate_harness.cpp(190): checkRep...
TRACE: deepstate_harness.cpp(192): RBTreeVerify...
TRACE: deepstate_harness.cpp(154): 46: SUCC:-244966140
TRACE: deepstate_harness.cpp(190): checkRep...
TRACE: deepstate_harness.cpp(192): RBTreeVerify...
TRACE: deepstate_harness.cpp(190): checkRep...
TRACE: deepstate_harness.cpp(192): RBTreeVerify...
TRACE: deepstate_harness.cpp(103): 48: INSERT:1679127713 0x00000000ffffffa4
TRACE: deepstate_harness.cpp(190): checkRep...
Assertion failed: (left_black_cnt == right_black_cnt), function checkRepHelper, file red_black_tree.c, line 702.
ERROR: Crashed: RBTree_GeneralFuzzer
INFO: Saved test case to file `tests/6de8b2ffd42af6878875833c0cbfa9ea09617285.crash`
...
real 0m0.148s
user 0m0.011s
sys 0m0.131s
為了方便本文的闡述,我做了一些精簡,上面的代碼只是詳細輸出的一部分。DeepState保與John的模糊器的最大區別在于,它保存了一個測試用例。當然,保存的測試用例的名稱會有所不同,因為每個已保存的測試都會生成唯一名稱。重放測試如下所示:
./ds_rb --input_test_file tests/6de8b2ffd42af6878875833c0cbfa9ea09617285.crash
而且我會再次看到整個過程細節,如上所述,這一長串看似任意的操作并不是最有幫助的測試。此時,DeepState可以幫助我們找到有用的信息。
deepstate-reduce ./ds_rb tests/6de8b2ffd42af6878875833c0cbfa9ea09617285.crash minimized.crash
ORIGINAL TEST HAS 8192 BYTES
LAST BYTE READ IS 509
SHRINKING TO IGNORE UNREAD BYTES
ONEOF REMOVAL REDUCED TEST TO 502 BYTES
ONEOF REMOVAL REDUCED TEST TO 494 BYTES
...
ONEOF REMOVAL REDUCED TEST TO 18 BYTES
ONEOF REMOVAL REDUCED TEST TO 2 BYTES
BYTE RANGE REMOVAL REDUCED TEST TO 1 BYTES
BYTE REDUCTION: BYTE 0 FROM 168 TO 0
NO (MORE) REDUCTIONS FOUND
PADDING TEST WITH 49 ZEROS
WRITING REDUCED TEST WITH 50 BYTES TO minimized.crash
同樣,我省略了減少測試的一些冗長過程,新的測試結果更容易理解。
./ds_rb --input_test_file minimized.crash
WARNING: No test specified, defaulting to last test defined (RBTree_GeneralFuzzer)
TRACE: Initialized test input buffer with data from `minimized.crash`
TRACE: Running: RBTree_GeneralFuzzer from deepstate_harness.cpp(78)
TRACE: deepstate_harness.cpp(103): 0: INSERT:0 0x0000000000000000
TRACE: deepstate_harness.cpp(190): checkRep...
TRACE: deepstate_harness.cpp(192): RBTreeVerify...
TRACE: deepstate_harness.cpp(103): 1: INSERT:0 0x0000000000000000
TRACE: deepstate_harness.cpp(190): checkRep...
TRACE: deepstate_harness.cpp(192): RBTreeVerify...
TRACE: deepstate_harness.cpp(103): 2: INSERT:0 0x0000000000000000
TRACE: deepstate_harness.cpp(190): checkRep...
Assertion failed: (left_black_cnt == right_black_cnt), function checkRepHelper, file red_black_tree.c, line 702.
ERROR: Crashed: RBTree_GeneralFuzzer
我們只需要在樹中插入三個相同的值就可以發現其中存在的漏洞,在繼續操作之前,請記得修復red_black_tree.c!
你可以點此鏈接(https://asciinema.org/a/220517.svg),觀看整個過程。
在第2部分中,我將對測試的結果進行評估,看看DeepState測試是否與John的模糊測試一樣有效?看看這兩種方法在查找某些潛在的漏洞方面是否同樣有效?符號執行是否也有同樣的效果呢?敬請關注。
總結
以上是生活随笔為你收集整理的测试页打印失败.是否参阅打印疑难解答以获得帮助_使用DeepState对API进行模糊测试(上)...的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 操作系统课设之基于信号量机制的并发程序设
- 下一篇: Python学习之Python安装