LibFuzzer workshop學習之路

2021-02-24 安全客

 

LibFuzzer workshop學習之路

最近做項目開始上手學習libfuzzer,是跟著libfuzzer-workshop學的。寫下自己的心得和收穫。

官方給出的定義

LibFuzzer is in-process, coverage-guided, evolutionary fuzzing engine.LibFuzzer is linked with the library under test, and feeds fuzzed inputs to the library via a specific fuzzing entrypoint (aka 「target function」); the fuzzer then tracks which areas of the code are reached, and generates mutations on the corpus of input data in order to maximize the code coverage. The code coverage information for libFuzzer is provided by LLVM’s SanitizerCoverage instrumentation.

簡單來說就是通過與要進行fuzz的庫連接,並將libfuzzer生成的輸入通過模糊測試進入點(fuzz target)餵給要fuzz的庫進行fuzz testing。同時fuzzer會跟蹤哪些區域的代碼已經被測試過的,並且根據種料庫的輸入進行變異來使得代碼覆蓋率最大化。代碼覆蓋率的信息是由LLVM’s SanitizerCoverage插樁提供的

需要注意的是這幾個libfuzzer的特性:in-process指進程內。即libfuzzer在fuzz時並不是產生出多個進程來分別處理不同的輸入,而是將所有的測試數據放入進程的內存空間中。coverage-guided指覆蓋率指導的。即會進行代碼覆蓋率的計算,正如定義所說的使得不斷增大代碼覆蓋率。evolutionary是指libfuzzer是進化型的fuzz,結合了產生和變異兩種形式。

環境搭建

跟著https://github.com/Dor1s/libfuzzer-workshop 搭建就好了,主要是build llvm的環境可能要make一會兒。編譯好後拿到libfuzzer.a(靜態連結庫文件),就可以開始上手實踐了。

fuzz testing

libfuzzer已經提供了數據樣本生成和異常檢測功能,我們要做的就是要實現模糊測試進入點(fuzz target),將libfuzzer生成的數據交給目標程序處理。
fuzz target編寫模板:

extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {  DoSomethingInterestingWithMyAPI(Data, Size);  return 0;  }

需要注意的是LLVMFuzzerTestOneInput函數即使我們要實現的接口函數,他的兩個參數Data(libfuzzer的測試樣本數據),size(樣本數據的大小)。
DoSomethingInterestingWithMyAPI函數即我們實際要進行fuzz的函數。

編譯.cc文件:

clang++ -g -O1 -fsanitize=fuzzer,address \fuzz_target.cc ../../libFuzzer/Fuzzer/libFuzzer.a \-o fuzzer_target

幾個參數:
-g 可選參數,保留調試符號。
-O1 指定優化等級為1
-fsanitize 指定sanitize。
fuzzer是必須的,用來啟用libfuzzer。還可以附加的其他sanitize有:address(用來檢測內存訪問相關的錯誤,如stack_overflow,heap_overflow,uaf,可以與fuzzer一起使用);memory(檢測未初始化內存的訪問,應單獨使用);undefined(檢測其他的漏洞,如整數溢出,類型混淆等未定義的漏洞)
註:-fsanitize-coverage=trace-pc-guard選項在高版本的clang中已不再適用,代碼的覆蓋率情況默認自動開啟。

這一步驟整體過程就是通過clang的-fsanitize=fuzzer選項可以啟用libFuzzer,這個選項在編譯和連結過程中生效,實現了條件判斷語句和分支執行的記錄,並且輔以libFuzzer中的庫函數(libfuzzer.a),通過生成不同的測試樣例然後能夠獲得代碼的覆蓋率情況,最終實現所謂的fuzz testing。

開始fuzz

先來lesson 04,要測試的庫是vulnerable_functions.h:


#ifndef LESSONS_04_VULNERABLE_FUNCTIONS_H_#define LESSONS_04_VULNERABLE_FUNCTIONS_H_
#include <stdint.h>#include <stddef.h>#include <cstring>
#include <array>#include <string>#include <vector>

bool VulnerableFunction1(const uint8_t* data, size_t size) { bool result = false; if (size >= 3) { result = data[0] == 'F' && data[1] == 'U' && data[2] == 'Z' && data[3] == 'Z'; }
return result;}

template<class T>typename T::value_type DummyHash(const T& buffer) { typename T::value_type hash = 0; for (auto value : buffer) hash ^= value;
return hash;}
constexpr auto kMagicHeader = "ZN_2016";constexpr std::size_t kMaxPacketLen = 1024;constexpr std::size_t kMaxBodyLength = 1024 - sizeof(kMagicHeader);
bool VulnerableFunction2(const uint8_t* data, size_t size, bool verify_hash) { if (size < sizeof(kMagicHeader)) return false;
std::string header(reinterpret_cast<const char*>(data), sizeof(kMagicHeader));
std::array<uint8_t, kMaxBodyLength> body;
if (strcmp(kMagicHeader, header.c_str())) return false;
auto target_hash = data[--size];
if (size > kMaxPacketLen) return false;
if (!verify_hash) return true;
std::copy(data, data + size, body.data()); auto real_hash = DummyHash(body); return real_hash == target_hash;}

constexpr std::size_t kZn2016VerifyHashFlag = 0x0001000;
bool VulnerableFunction3(const uint8_t* data, size_t size, std::size_t flags) { bool verify_hash = flags & kZn2016VerifyHashFlag; return VulnerableFunction2(data, size, verify_hash);}

#endif

首先看VulnerableFunction1(),有兩個參數data/size,當size>3時會產生數組越界。接下來編寫測試接口:


#include <stdint.h>#include <stddef.h>
#include "vulnerable_functions.h"
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { VulnerableFunction1(data, size); return 0;}

可以看到,直接將Libfuzzer生成的測試樣例給到VulnerableFunction1就好。
接下來編譯:
clang++ -g -std=c++11 -fsanitize=fuzzer,address first_fuzzer.cc ../../libFuzzer/Fuzzer/libFuzzer.a -o first_fuzzer
生成可執行程序first_fuzzer。

mkdir corpus1./first_fuzz corpus1

corpus1是我們提供的語料庫。理想情況下,該語料庫應該為被測代碼提供各種有效和無效的輸入,模糊器基於當前語料庫中的樣本輸入生成隨機突變。如果突變觸發了測試代碼中先前未覆蓋的路徑的執行,則該突變將保存到語料庫中以供將來變更。當然LibFuzzer也可以沒有任何初始種子的情況下工作(因為上面提到他是evolutionary型的fuzzer),但如果受測試的庫接受複雜的結構化輸入,則會因為隨機產生的樣例不易符合導致效率降低。
另外,如果我們有太多的樣例並希望能夠精簡一下,則可以:

mkdir corpus1_min./first_fuzzer -merge=1 corpus1_min corpus1

這樣,corpus1_min將會存放精簡後的輸入樣例。

運行後得到crash,很快啊!!!

➜  04 git:(master) ✗ ./first_fuzzer corpus1 INFO: Seed: 2222548757INFO: Loaded 1 modules   (35 inline 8-bit counters): 35 [0x7f7120, 0x7f7143), INFO: Loaded 1 PC tables (35 PCs): 35 [0x5b7a68,0x5b7c98), INFO:        0 files found in corpus1INFO: -max_len is not provided; libFuzzer will not generate inputs larger than 4096 bytesINFO: A corpus is not provided, starting from an empty corpus#2    INITED cov: 3 ft: 3 corp: 1/1b exec/s: 0 rss: 27Mb#3    NEW    cov: 4 ft: 4 corp: 2/4b lim: 4 exec/s: 0 rss: 27Mb L: 3/3 MS: 1 CMP- DE: "\x00\x00"-#1190    NEW    cov: 5 ft: 5 corp: 3/7b lim: 14 exec/s: 0 rss: 27Mb L: 3/3 MS: 2 ChangeBinInt-CMP- DE: "F\x00"-#12191    NEW    cov: 6 ft: 6 corp: 4/11b lim: 122 exec/s: 0 rss: 28Mb L: 4/4 MS: 1 InsertByte-#12297    REDUCE cov: 6 ft: 6 corp: 4/10b lim: 122 exec/s: 0 rss: 28Mb L: 3/3 MS: 1 EraseBytes-#17213    REDUCE cov: 7 ft: 7 corp: 5/40b lim: 170 exec/s: 0 rss: 29Mb L: 30/30 MS: 1 InsertRepeatedBytes-#17274    REDUCE cov: 7 ft: 7 corp: 5/27b lim: 170 exec/s: 0 rss: 29Mb L: 17/17 MS: 1 EraseBytes-#17356    REDUCE cov: 7 ft: 7 corp: 5/24b lim: 170 exec/s: 0 rss: 29Mb L: 14/14 MS: 2 ChangeBit-EraseBytes-#17437    REDUCE cov: 7 ft: 7 corp: 5/18b lim: 170 exec/s: 0 rss: 29Mb L: 8/8 MS: 1 EraseBytes-#17458    REDUCE cov: 7 ft: 7 corp: 5/14b lim: 170 exec/s: 0 rss: 29Mb L: 4/4 MS: 1 EraseBytes-===================================================================9875==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x60200005a1d3 at pc 0x00000059b461 bp 0x7ffd79c84880 sp 0x7ffd79c84878READ of size 1 at 0x60200005a1d3 thread T0    #0 0x59b460 in VulnerableFunction1(unsigned char const*, unsigned long) /home/admin/libfuzzer-workshop/lessons/04/./vulnerable_functions.h:22:14    #1 0x59bde4 in LLVMFuzzerTestOneInput /home/admin/libfuzzer-workshop/lessons/04/first_fuzzer.cc:10:3    #2 0x466186 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) /home/admin/libfuzzer-workshop/src/llvm/projects/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:556    #3 0x46b7e9 in fuzzer::Fuzzer::RunOne(unsigned char const*, unsigned long, bool, fuzzer::InputInfo*, bool*) /home/admin/libfuzzer-workshop/src/llvm/projects/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:470    #4 0x46b7e9 in fuzzer::Fuzzer::MutateAndTestOne() /home/admin/libfuzzer-workshop/src/llvm/projects/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:699    #5 0x46e80f in fuzzer::Fuzzer::Loop(std::Fuzzer::vector<fuzzer::SizedFile, fuzzer::fuzzer_allocator<fuzzer::SizedFile> >&) /home/admin/libfuzzer-workshop/src/llvm/projects/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:830    #6 0x456b99 in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) /home/admin/libfuzzer-workshop/src/llvm/projects/compiler-rt/lib/fuzzer/FuzzerDriver.cpp:824    #7 0x41f522 in main /home/admin/libfuzzer-workshop/src/llvm/projects/compiler-rt/lib/fuzzer/FuzzerMain.cpp:19    #8 0x7fbfaac5abf6 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x21bf6)    #9 0x41f599 in _start (/home/admin/libfuzzer-workshop/lessons/04/first_fuzzer+0x41f599)
0x60200005a1d3 is located 0 bytes to the right of 3-byte region [0x60200005a1d0,0x60200005a1d3)allocated by thread T0 here: #0 0x597b58 in operator new[](unsigned long) /home/admin/libfuzzer-workshop/src/llvm/projects/compiler-rt/lib/asan/asan_new_delete.cpp:102 #1 0x466092 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) /home/admin/libfuzzer-workshop/src/llvm/projects/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:541 #2 0x46b7e9 in fuzzer::Fuzzer::RunOne(unsigned char const*, unsigned long, bool, fuzzer::InputInfo*, bool*) /home/admin/libfuzzer-workshop/src/llvm/projects/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:470 #3 0x46b7e9 in fuzzer::Fuzzer::MutateAndTestOne() /home/admin/libfuzzer-workshop/src/llvm/projects/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:699 #4 0x46e80f in fuzzer::Fuzzer::Loop(std::Fuzzer::vector<fuzzer::SizedFile, fuzzer::fuzzer_allocator<fuzzer::SizedFile> >&) /home/admin/libfuzzer-workshop/src/llvm/projects/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:830 #5 0x456b99 in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) /home/admin/libfuzzer-workshop/src/llvm/projects/compiler-rt/lib/fuzzer/FuzzerDriver.cpp:824 #6 0x41f522 in main /home/admin/libfuzzer-workshop/src/llvm/projects/compiler-rt/lib/fuzzer/FuzzerMain.cpp:19 #7 0x7fbfaac5abf6 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x21bf6)
SUMMARY: AddressSanitizer: heap-buffer-overflow /home/admin/libfuzzer-workshop/lessons/04/./vulnerable_functions.h:22:14 in VulnerableFunction1(unsigned char const*, unsigned long)Shadow bytes around the buggy address: 0x0c04800033e0: fa fa fd fa fa fa fd fd fa fa fd fd fa fa fd fd 0x0c04800033f0: fa fa fd fd fa fa fd fa fa fa fd fa fa fa fd fd 0x0c0480003400: fa fa fd fa fa fa fd fa fa fa fd fa fa fa fd fa 0x0c0480003410: fa fa fd fa fa fa fd fa fa fa fd fa fa fa fd fa 0x0c0480003420: fa fa fd fa fa fa fd fa fa fa fd fa fa fa fd fa=>0x0c0480003430: fa fa fd fa fa fa fd fa fa fa[03]fa fa fa fa fa 0x0c0480003440: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c0480003450: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c0480003460: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c0480003470: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c0480003480: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa faShadow byte legend (one shadow byte represents 8 application bytes): Addressable: 00 Partially addressable: 01 02 03 04 05 06 07 Heap left redzone: fa Freed heap region: fd Stack left redzone: f1 Stack mid redzone: f2 Stack right redzone: f3 Stack after return: f5 Stack use after scope: f8 Global redzone: f9 Global init order: f6 Poisoned by user: f7 Container overflow: fc Array cookie: ac Intra object redzone: bb ASan internal: fe Left alloca redzone: ca Right alloca redzone: cb Shadow gap: cc==9875==ABORTINGMS: 1 EraseBytes-; base unit: aea2e3923af219a8956f626558ef32f30a914ebc0x46,0x55,0x5a,FUZartifact_prefix='./'; Test unit written to ./crash-0eb8e4ed029b774d80f2b66408203801cb982a60Base64: RlVa

需要注意的地方有點多:
前面的幾行輸出fuzzer相關的選項和配置信息。seed=2222548757是生成的隨機數種子,可以利用./first_fuzzer -seed=2222548757指定隨機種子。-max_len為測試輸入的最大長度

以#開頭的表示在fuzz的過程中覆蓋的路徑信息。
INITED fuzzer已完成初始化,其中包括通過被測代碼運行每個初始輸入樣本。
READ fuzzer已從語料庫目錄中讀取了所有提供的輸入樣本。
NEW fuzzer創建了一個測試輸入,該輸入涵蓋了被測代碼的新區域。此輸入將保存到主要語料庫目錄。
pulse fuzzer已生成 2的n次方個輸入(定期生成以使用戶確信fuzzer仍在工作)。
REDUCE fuzzer發現了一個更好(更小)的輸入,可以觸發先前發現的特徵(設置-reduce_inputs=0以禁用)。
cov 執行當前語料庫所覆蓋的代碼塊或邊的總數。
ft libFuzzer使用不同的信號來評估代碼覆蓋率:邊緣覆蓋率,邊緣計數器,值配置文件,間接調用方/被調用方對等。這些組合的信號稱為功能(ft:)。
corp 當前內存中測試語料庫中的條目數及其大小(以字節為單位)。
exec/s 每秒模糊器迭代的次數。
rss 當前的內存消耗。
L 新輸入的大小(以字節為單位)。
MS:<n> <操作> 計數和用於生成輸入的變異操作列表

#17458 REDUCE cov: 7 ft: 7 corp: 5/14b lim: 170 exec/s: 0 rss: 29Mb L: 4/4 MS: 1 EraseBytes-
指嘗試了17458個輸入,成功發現了5個樣本(放入語料庫)大小為14b,共覆蓋了7個代碼塊,佔用內存29mb,變異操作為EraseBytes-。

接下來就是異常檢測相關的信息:

==9875==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x60200005a1d3 at pc 0x00000059b461 bp 0x7ffd79c84880 sp 0x7ffd79c84878
READ of size 1 at 0x60200005a1d3 thread T0

可以看到AddressSanitizer檢測到其中的一個輸入觸發了堆溢出(heap-buffer-overflow)的漏洞。
更據錯誤信息中的#0 0x59b460 in VulnerableFunction1(unsigned char const*, unsigned long) /home/admin/libfuzzer-workshop/lessons/04/./vulnerable_functions.h:22:14可以看到錯誤點在vulnerable_functions.h:22:14,對應data[3] == 'Z';即數組越界的位置。下面的SUMMARY也與之對應。

倒數第二行給出了造成crash的輸入,並將其寫入了crash-0eb8e4ed029b774d80f2b66408203801cb982a60。
復現crash可執行./first_fuzzer ./crash-0eb8e4ed029b774d80f2b66408203801cb982a60。

這樣fuzz tesing基本上已經完成了,我們得到了一個造成程序crash的輸入,並得知存在堆溢出的漏洞。這樣我們就可以有針對性的對程序進行動態調試,利用造成crash的輸入回溯出漏洞的細節。

繼續fuzz函數VulnerableFunction2

constexpr auto kMagicHeader = "ZN_2016";constexpr std::size_t kMaxPacketLen = 1024;constexpr std::size_t kMaxBodyLength = 1024 - sizeof(kMagicHeader);
bool VulnerableFunction2(const uint8_t* data, size_t size, bool verify_hash) { if (size < sizeof(kMagicHeader)) return false;
std::string header(reinterpret_cast<const char*>(data), sizeof(kMagicHeader));
std::array<uint8_t, kMaxBodyLength> body;
if (strcmp(kMagicHeader, header.c_str())) return false;
auto target_hash = data[--size];
if (size > kMaxPacketLen) return false;
if (!verify_hash) return true;
std::copy(data, data + size, body.data()); auto real_hash = DummyHash(body); return real_hash == target_hash;}

該函數多了一個bool參數,因此我們的的接口函數要有所改動:

#include <stdint.h>#include <stddef.h>
#include "vulnerable_functions.h"
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { bool verify_hash_flags[] = {true, false}; for(auto flag : verify_hash_flags) VulnerableFunction2(data, size, flag); return 0;}

為了提高fuzz出crash的概率,我們要分別fuzz flag為true和false的請況,而不應該把flag寫死。
接著編譯並執行得到:

INFO: Seed: 296692635INFO: Loaded 1 modules   (37 inline 8-bit counters): 37 [0x7f8160, 0x7f8185), INFO: Loaded 1 PC tables (37 PCs): 37 [0x5b7d00,0x5b7f50), INFO:        0 files found in corpus2INFO: -max_len is not provided; libFuzzer will not generate inputs larger than 4096 bytesINFO: A corpus is not provided, starting from an empty corpus#2    INITED cov: 5 ft: 6 corp: 1/1b exec/s: 0 rss: 27Mb#414    NEW    cov: 6 ft: 7 corp: 2/9b lim: 8 exec/s: 0 rss: 27Mb L: 8/8 MS: 2 ChangeByte-CrossOver-    NEW_FUNC[1/13]: 0x59bab0 in unsigned char* std::copy<unsigned char const*, unsigned char*>(unsigned char const*, unsigned char const*, unsigned char*) /usr/lib/gcc/x86_64-linux-gnu/7.5.0/../../../../include/c++/7.5.0/bits/stl_algobase.h:447    NEW_FUNC[2/13]: 0x59bb40 in std::array<unsigned char, 1016ul>::data() /usr/lib/gcc/x86_64-linux-gnu/7.5.0/../../../../include/c++/7.5.0/array:235#584    NEW    cov: 24 ft: 26 corp: 3/17b lim: 8 exec/s: 0 rss: 28Mb L: 8/8 MS: 5 CopyPart-CopyPart-ShuffleBytes-CrossOver-CMP- DE: "ZN_2016"-#105505    NEW    cov: 25 ft: 27 corp: 4/1067b lim: 1050 exec/s: 0 rss: 47Mb L: 1050/1050 MS: 1 CrossOver-#106508    REDUCE cov: 25 ft: 27 corp: 4/1046b lim: 1050 exec/s: 0 rss: 48Mb L: 1029/1029 MS: 3 EraseBytes-ChangeBit-CopyPart-#108257    REDUCE cov: 25 ft: 27 corp: 4/1043b lim: 1060 exec/s: 0 rss: 49Mb L: 1026/1026 MS: 4 ChangeByte-ChangeByte-CrossOver-InsertRepeatedBytes-===================================================================10468==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7fff31422548 at pc 0x00000055286d bp 0x7fff31421f90 sp 0x7fff31421740WRITE of size 1023 at 0x7fff31422548 thread T0    #0 0x55286c in __asan_memmove /home/admin/libfuzzer-workshop/src/llvm/projects/compiler-rt/lib/asan/asan_interceptors_memintrinsics.cpp:30    #1 0x59c238 in unsigned char* std::__copy_move<false, true, std::random_access_iterator_tag>::__copy_m<unsigned char>(unsigned char const*, unsigned char const*, unsigned char*) /usr/lib/gcc/x86_64-linux-gnu/7.5.0/../../../../include/c++/7.5.0/bits/stl_algobase.h:368:6    #2 0x59c158 in unsigned char* std::__copy_move_a<false, unsigned char const*, unsigned char*>(unsigned char const*, unsigned char const*, unsigned char*) /usr/lib/gcc/x86_64-linux-gnu/7.5.0/../../../../include/c++/7.5.0/bits/stl_algobase.h:385:14    #3 0x59c0b6 in unsigned char* std::__copy_move_a2<false, unsigned char const*, unsigned char*>(unsigned char const*, unsigned char const*, unsigned char*) /usr/lib/gcc/x86_64-linux-gnu/7.5.0/../../../../include/c++/7.5.0/bits/stl_algobase.h:422:18    #4 0x59bb39 in unsigned char* std::copy<unsigned char const*, unsigned char*>(unsigned char const*, unsigned char const*, unsigned char*) /usr/lib/gcc/x86_64-linux-gnu/7.5.0/../../../../include/c++/7.5.0/bits/stl_algobase.h:454:15    #5 0x59b8b5 in VulnerableFunction2(unsigned char const*, unsigned long, bool) /home/admin/libfuzzer-workshop/lessons/04/./vulnerable_functions.h:61:3    #6 0x59bf63 in LLVMFuzzerTestOneInput /home/admin/libfuzzer-workshop/lessons/04/second_fuzzer.cc:12:2    #7 0x466186 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) /home/admin/libfuzzer-workshop/src/llvm/projects/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:556    #8 0x46b7e9 in fuzzer::Fuzzer::RunOne(unsigned char const*, unsigned long, bool, fuzzer::InputInfo*, bool*) /home/admin/libfuzzer-workshop/src/llvm/projects/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:470    #9 0x46b7e9 in fuzzer::Fuzzer::MutateAndTestOne() /home/admin/libfuzzer-workshop/src/llvm/projects/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:699    #10 0x46e80f in fuzzer::Fuzzer::Loop(std::Fuzzer::vector<fuzzer::SizedFile, fuzzer::fuzzer_allocator<fuzzer::SizedFile> >&) /home/admin/libfuzzer-workshop/src/llvm/projects/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:830    #11 0x456b99 in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) /home/admin/libfuzzer-workshop/src/llvm/projects/compiler-rt/lib/fuzzer/FuzzerDriver.cpp:824    #12 0x41f522 in main /home/admin/libfuzzer-workshop/src/llvm/projects/compiler-rt/lib/fuzzer/FuzzerMain.cpp:19    #13 0x7fd1b4bf6bf6 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x21bf6)    #14 0x41f599 in _start (/home/admin/libfuzzer-workshop/lessons/04/second_fuzzer+0x41f599)
Address 0x7fff31422548 is located in stack of thread T0 at offset 1128 in frame #0 0x59b4af in VulnerableFunction2(unsigned char const*, unsigned long, bool) /home/admin/libfuzzer-workshop/lessons/04/./vulnerable_functions.h:42
This frame has 3 object(s): [32, 64) 'header' (line 46) [96, 97) 'ref.tmp' (line 46) [112, 1128) 'body' (line 48) <== Memory access at offset 1128 overflows this variableHINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork (longjmp and C++ exceptions *are* supported)SUMMARY: AddressSanitizer: stack-buffer-overflow /home/admin/libfuzzer-workshop/src/llvm/projects/compiler-rt/lib/asan/asan_interceptors_memintrinsics.cpp:30 in __asan_memmoveShadow bytes around the buggy address: 0x10006627c450: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x10006627c460: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x10006627c470: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x10006627c480: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x10006627c490: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00=>0x10006627c4a0: 00 00 00 00 00 00 00 00 00[f3]f3 f3 f3 f3 f3 f3 0x10006627c4b0: f3 f3 f3 f3 f3 f3 f3 f3 f3 f3 f3 f3 00 00 00 00 0x10006627c4c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x10006627c4d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x10006627c4e0: 00 00 00 00 f1 f1 f1 f1 02 f3 f3 f3 00 00 00 00 0x10006627c4f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00Shadow byte legend (one shadow byte represents 8 application bytes): Addressable: 00 Partially addressable: 01 02 03 04 05 06 07 Heap left redzone: fa Freed heap region: fd Stack left redzone: f1 Stack mid redzone: f2 Stack right redzone: f3 Stack after return: f5 Stack use after scope: f8 Global redzone: f9 Global init order: f6 Poisoned by user: f7 Container overflow: fc Array cookie: ac Intra object redzone: bb ASan internal: fe Left alloca redzone: ca Right alloca redzone: cb Shadow gap: cc==10468==ABORTINGMS: 4 ChangeBinInt-CrossOver-ChangeByte-EraseBytes-; base unit: e63bf1b07c6950248bb14fe74c3a84f06c711b15artifact_prefix='./'; Test unit written to ./crash-a42cbe2ff7331f281ef213e54919e8cd932883bd

相信大家已經不再陌生了,定位錯誤位於#5 0x59b8b5 in VulnerableFunction2(unsigned char const*, unsigned long, bool) /home/admin/libfuzzer-workshop/lessons/04/./vulnerable_functions.h:61:3
即std::copy(data, data + size, body.data());,該句造成了stack_overflow,原因在於vector類型的body的大小為1024 – sizeof(kMagicHeader),而在copy時的data的size的限制條件是size > kMaxPacketLen(1024),從而造成了緩衝區溢出。

如果我們寫死flag為false的話就可能會跑很久,也跑不出來crash。因此,我們在套用模板時要結合函數的邏輯,使得fuzz的接口設計的更加合理,從而增加fuzz的效率。

繼續繼續VulnerableFunction3:

constexpr std::size_t kZn2016VerifyHashFlag = 0x0001000;
bool VulnerableFunction3(const uint8_t* data, size_t size, std::size_t flags) { bool verify_hash = flags & kZn2016VerifyHashFlag; return VulnerableFunction2(data, size, verify_hash);}

可以看到,與2不同的地方就是對flag進行了& kZn2016VerifyHashFlag的計算,這種其實我們可以計算出使得flags & kZn2016VerifyHashFlag為true/false的輸入,並模仿second_fuzzer那樣寫個循環,這樣就和2沒差了。
但這裡workshop提供了一個不同的方法:In this case, we can get some randomization of flags values using data provided by libFuzzer:

#include <stdint.h>#include <stddef.h>
#include "vulnerable_functions.h"
#include <functional>#include <string>
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { std::string data_string(reinterpret_cast<const char*>(data), size); auto data_hash = std::hash<std::string>()(data_string);
std::size_t flags = static_cast<size_t>(data_hash); VulnerableFunction3(data, size, flags); return 0;}

採用了libfuzzer提供的隨機化方法使得我們的輸入flag為隨機數,從而flags & kZn2016VerifyHashFlag的結果也隨機化。在次數足夠多的情況下false和true的情況將趨於同為50%。

接下來看lesson05:

這次的目標和lesson04有所不同,lesson04中我們針對.h函數庫中的函數進行fuzz,而libfuzzer的威力遠不止於此,它還可以對大型開源庫進行模糊測試。在源碼編譯開源庫時選擇合適的選項以及將libfuzzer與開源庫連結在一起以進行fuzz,這些細節將在該lesson05體現。

首先解包tar xzf openssl1.0.1f.tgz。接著執行./config生成makefile。之後:

make cleanmake CC="clang -O2 -fno-omit-frame-pointer -g -fsanitize=address" -j$(nproc)

第二條指令其實並不太規範,將編輯器以外的其他參數也一股腦寫到CC變量裡了。clang後的參數本應該是由CFLAGS或CXXFLAGS指定的。解釋一下選項:

-02 指定優先級別
-g 帶有調試符號來編譯
-fno-omit-frame-pointer 對於不需要棧指針的函數就不在寄存器中保存指針,因此可以忽略存儲和檢索地址的代碼,同時對許多函數提供一個額外的寄存器。所有」-O」級別都打開它,但僅在調試器可以不依靠棧指針運行時才有效。在AMD64平臺上此選項默認打開,但是在x86平臺上則默認關閉。建議顯式的設置它。
-fsanitize 指定sanitize

這些參數的指定是至關重要的,它會影響到之後開源庫與libfuzzer的連結以及fuzz的效率。如果不設置這些編譯選項直接make的話fuzz的效率如下:

➜  05 git:(master) ✗ ./openssl_fuzzer2INFO: Seed: 1865248494INFO: Loaded 1 modules   (10 inline 8-bit counters): 10 [0x96c950, 0x96c95a), INFO: Loaded 1 PC tables (10 PCs): 10 [0x6d56d0,0x6d5770), INFO: -max_len is not provided; libFuzzer will not generate inputs larger than 4096 bytesINFO: A corpus is not provided, starting from an empty corpus#2    INITED cov: 2 ft: 3 corp: 1/1b exec/s: 0 rss: 29Mb#131072    pulse  cov: 2 ft: 3 corp: 1/1b lim: 1300 exec/s: 43690 rss: 388Mb#262144    pulse  cov: 2 ft: 3 corp: 1/1b lim: 2611 exec/s: 43690 rss: 397Mb#524288    pulse  cov: 2 ft: 3 corp: 1/1b lim: 4096 exec/s: 37449 rss: 411Mb#1048576    pulse  cov: 2 ft: 3 corp: 1/1b lim: 4096 exec/s: 34952 rss: 411Mb#2097152    pulse  cov: 2 ft: 3 corp: 1/1b lim: 4096 exec/s: 32768 rss: 411Mb

這路徑覆蓋率太感人,其中的重要原因就在於編譯開源庫時的選項設置。
設置編譯選項後的fuzz效果:

INFO: Seed: 2565779026INFO: Loaded 1 modules   (35878 inline 8-bit counters): 35878 [0xcd8590, 0xce11b6), INFO: Loaded 1 PC tables (35878 PCs): 35878 [0x954da8,0x9e1008), INFO: -max_len is not provided; libFuzzer will not generate inputs larger than 4096 bytesINFO: A corpus is not provided, starting from an empty corpus#2    INITED cov: 410 ft: 411 corp: 1/1b exec/s: 0 rss: 36Mb#112    NEW    cov: 413 ft: 416 corp: 2/2b lim: 4 exec/s: 0 rss: 42Mb L: 1/1 MS: 5 ChangeBit-ChangeBinInt-ChangeByte-InsertByte-EraseBytes-    NEW_FUNC[1/1]: 0x65f0a0 in ERR_put_error /home/admin/libfuzzer-workshop/lessons/05/openssl1.0.1f/crypto/err/err.c:708#319    NEW    cov: 420 ft: 448 corp: 3/8b lim: 6 exec/s: 0 rss: 51Mb L: 6/6 MS: 2 ShuffleBytes-CrossOver-#327    REDUCE cov: 420 ft: 448 corp: 3/7b lim: 6 exec/s: 0 rss: 51Mb L: 5/5 MS: 3 ChangeByte-EraseBytes-CrossOver-    NEW_FUNC[1/2]: 0x562a40 in tls1_enc /home/admin/libfuzzer-workshop/lessons/05/openssl1.0.1f/ssl/t1_enc.c:685    NEW_FUNC[2/2]: 0x67e1d0 in EVP_MD_CTX_md /home/admin/libfuzzer-workshop/lessons/05/openssl1.0.1f/crypto/evp/evp_lib.c:282#454    REDUCE cov: 431 ft: 467 corp: 4/12b lim: 6 exec/s: 0 rss: 57Mb L: 5/5 MS: 2 ShuffleBytes-CMP- DE: "\xfd\x03\x00\x00"-    NEW_FUNC[1/6]: 0x5663d0 in tls1_alert_code /home/admin/libfuzzer-workshop/lessons/05/openssl1.0.1f/ssl/t1_enc.c:1214    NEW_FUNC[2/6]: 0x5c6560 in do_ssl3_write /home/admin/libfuzzer-workshop/lessons/05/openssl1.0.1f/ssl/s3_pkt.c:634#470    NEW    cov: 465 ft: 514 corp: 5/17b lim: 6 exec/s: 0 rss: 58Mb L: 5/5 MS: 1 ChangeByte-#472    REDUCE cov: 465 ft: 521 corp: 6/23b lim: 6 exec/s: 0 rss: 58Mb L: 6/6 MS: 2 CrossOver-CrossOver-#475    NEW    cov: 467 ft: 523 corp: 7/28b lim: 6 exec/s: 0 rss: 58Mb L: 5/6 MS: 3 PersAutoDict-ChangeByte-ShuffleBytes- DE: "\xfd\x03\x00\x00"-#1025    NEW    cov: 467 ft: 526 corp: 8/39b lim: 11 exec/s: 0 rss: 81Mb L: 11/11 MS: 5 CopyPart-ShuffleBytes-CrossOver-ShuffleBytes-ChangeBinInt-#1097    NEW    cov: 474 ft: 549 corp: 9/49b lim: 11 exec/s: 0 rss: 84Mb L: 10/11 MS: 2 ChangeBit-CopyPart-#1293    REDUCE cov: 474 ft: 549 corp: 9/48b lim: 11 exec/s: 0 rss:92Mb L: 10/10 MS: 1 EraseBytes-

選擇合適的編譯器和編譯選項,完成對該庫的源碼編譯,生成.a文件。接下來就要研究編寫fuzzer接口函數了。
workshop提供了openssl_fuzzer.cc:

#include <openssl/ssl.h>#include <openssl/err.h>#include <assert.h>#include <stdint.h>#include <stddef.h>
#ifndef CERT_PATH# define CERT_PATH#endif
SSL_CTX *Init() { SSL_library_init(); SSL_load_error_strings(); ERR_load_BIO_strings(); OpenSSL_add_all_algorithms(); SSL_CTX *sctx; assert (sctx = SSL_CTX_new(TLSv1_method())); assert(SSL_CTX_use_certificate_file(sctx, CERT_PATH "server.pem", SSL_FILETYPE_PEM)); assert(SSL_CTX_use_PrivateKey_file(sctx, CERT_PATH "server.key", SSL_FILETYPE_PEM)); return sctx;}
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { static SSL_CTX *sctx = Init(); SSL *server = SSL_new(sctx); BIO *sinbio = BIO_new(BIO_s_mem()); BIO *soutbio = BIO_new(BIO_s_mem()); SSL_set_bio(server, sinbio, soutbio); SSL_set_accept_state(server); BIO_write(sinbio, Data, Size); SSL_do_handshake(server); SSL_free(server); return 0;}

這裡就涉及到openssl庫提供的相關方法了,本篇主要講解fuzz相關,就不細講openssl了。總之就是要先搞清楚openssl的用法,再通過include openssl提供的函數來對openssl進行fuzz。編譯如下:

clang++ -g openssl_fuzzer.cc -O2 -fno-omit-frame-pointer -fsanitize=address,fuzzer \    -I openssl1.0.1f/include openssl1.0.1f/libssl.a openssl1.0.1f/libcrypto.a \    ../../libFuzzer/Fuzz/libFuzzer.a -o openssl_fuzzer

-I指定inlcude的搜索路徑,同時連結靜態庫libcrypto.a和libFuzzer.a以使用庫中的函數。

運行跑出crash:

===================================================================2133==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x629000009748 at pc 0x00000051eaba bp 0x7ffd255dae50 sp 0x7ffd255da618READ of size 21050 at 0x629000009748 thread T0                                                            
0x629000009748 is located 0 bytes to the right of 17736-byte region [0x629000005200,0x629000009748)allocated by thread T0 here:
SUMMARY: AddressSanitizer: heap-buffer-overflow /local/mnt/workspace/bcain_clang_bcain-ubuntu_23113/llvm/utils/release/final/llvm.src/projects/compiler-rt/lib/asan/asan_interceptors_memintrinsics.cc:22:3 in __asan_memcpyShadow bytes around the buggy address: 0x0c527fff9290: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0c527fff92a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0c527fff92b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0c527fff92c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0c527fff92d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00=>0x0c527fff92e0: 00 00 00 00 00 00 00 00 00[fa]fa fa fa fa fa fa 0x0c527fff92f0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c527fff9300: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c527fff9310: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c527fff9320: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c527fff9330: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa faShadow byte legend (one shadow byte represents 8 application bytes): Addressable: 00 Partially addressable: 01 02 03 04 05 06 07 Heap left redzone: fa Freed heap region: fd Stack left redzone: f1 Stack mid redzone: f2 Stack right redzone: f3 Stack after return: f5 Stack use after scope: f8 Global redzone: f9 Global init order: f6 Poisoned by user: f7 Container overflow: fc Array cookie: ac Intra object redzone: bb ASan internal: fe Left alloca redzone: ca Right alloca redzone: cb Shadow gap: cc==2133==ABORTINGMS: 2 ChangeBinInt-InsertByte-; base unit: 7bfa057a7ae6a7bc6ea487749407e4e7d4e9bf840x15,0x3,0xd4,0x0,0x7,0x1,0x3a,0x1,0x3a,0x1,0xd3,0xb3,0x18,0x3,0xd4,0x0,0x7,0x1,0x52,0x3a,0x1,0x3a,0x9,0xd3,0xb3,0x18,0x2c,0x0,0x7,0x2,0x7,0x1,0x3a,0x1,0x0,\x15\x03\xd4\x00\x07\x01:\x01:\x01\xd3\xb3\x18\x03\xd4\x00\x07\x01R:\x01:\x09\xd3\xb3\x18,\x00\x07\x02\x07\x01:\x01\x00artifact_prefix='./'; Test unit written to ./crash-4bfb53055de3fed318590b20ddd4cda0d95e8ed8Base64: FQPUAAcBOgE6AdOzGAPUAAcBUjoBOgnTsxgsAAcCBwE6AQA=

通過SUMMARY容易找到造成heap_overflow的漏洞點在於:/local/mnt/workspace/bcain_clang_bcain-ubuntu_23113/llvm/utils/release/final/llvm.src/projects/compiler-rt/lib/asan/asan_interceptors_memintrinsics.cc:22:3 in __asan_memcpy這種目錄一看就是很底層的目錄,不易定位。再往找一層:#1 0x55e323 in tls1_process_heartbeat /home/admin/libfuzzer-workshop/lessons/05/openssl1.0.1f/ssl/t1_lib.c:2586:3這就很容易定位了,接下來就是漏洞溯源了。
定位到了代碼:

tls1_process_heartbeat(SSL *s)    {    unsigned char *p = &s->s3->rrec.data[0], *pl;    unsigned short hbtype;    unsigned int payload;    unsigned int padding = 16; 
hbtype = *p++; n2s(p, payload); pl = p;
if (s->msg_callback) s->msg_callback(0, s->version, TLS1_RT_HEARTBEAT, &s->s3->rrec.data[0], s->s3->rrec.length, s, s->msg_callback_arg);
if (hbtype == TLS1_HB_REQUEST) { unsigned char *buffer, *bp; int r;
buffer = OPENSSL_malloc(1 + 2 + payload + padding); bp = buffer;
*bp++ = TLS1_HB_RESPONSE; s2n(payload, bp); memcpy(bp, pl, payload); bp += payload; RAND_pseudo_bytes(bp, padding);
r = ssl3_write_bytes(s, TLS1_RT_HEARTBEAT, buffer, 3 + payload + padding);
if (r >= 0 && s->msg_callback) s->msg_callback(1, s->version, TLS1_RT_HEARTBEAT, buffer, 3 + payload + padding, s, s->msg_callback_arg);
OPENSSL_free(buffer);
if (r < 0) return r; } else if (hbtype == TLS1_HB_RESPONSE) { unsigned int seq;
n2s(pl, seq);
if (payload == 18 && seq == s->tlsext_hb_seq) { s->tlsext_hb_seq++; s->tlsext_hb_pending = 0; } }
return 0; }

漏洞點再memcpy(bp, pl, payload);根據crash原因為over_flow說明payload的長度有問題。往上分析發現payload是通過n2s函數從p指向的結構體(用戶數據包)內容得到的。該結構體為:

typedef struct ssl3_record_st    {        int type;               / type of record /        unsigned int length;    / How many bytes available /        unsigned int off;       / read/write offset into 'buf' /        unsigned char data;    / pointer to the record data /        unsigned char input;   / where the decode bytes are /        unsigned char comp;    / only used with decompression - malloc()ed /        unsigned long epoch;    / epoch number, needed by DTLS1 /        unsigned char seq_num[8]; / sequence number, needed by DTLS1 /    } SSL3_RECORD;

而程序並沒有對用戶可控的length做檢查,從而導致memcpy溢出(有可能將server端的數據寫入到返回數據包中返回給用戶)。

初學libfuzzer,如有紕漏錯誤之後還煩請師傅們指正。

相關焦點

  • fuzz實戰之libfuzzer
    ://github.com/Dor1s/libfuzzer-workshop.gitsudo ln -s /usr/include/asm-generic /usr/include/asmapt-get install gcc-multilib 然後進入 libfuzzer-workshop/ , 執行 checkout_build_install_llvm.sh 安裝好 llvm
  • 漏洞挖掘|開源Fuzzer和Fuzzing學習資源整理
    最近一直在做fuzzing,所以整理一下模糊測試相關的工具和思路,很多我也在學習研究,有些還沒來得及學習,Fuzzing三要素是目標、策略
  • cpython歷史漏洞分析及其fuzzer編寫
    (gdb) r poc.pyStarting program: /root/cpython/python poc.py[Thread debugging using libthread_db enabled]Using host libthread_db library "/lib/i386-linux-gnu/libthread_db.so.1".
  • MikroTik-SMB 測試之 Mutiny-Fuzzer
    Mutiny是由思科研究人員開發的一款基於變異的網絡fuzz框架,其主要原理是通過從數據包(如pcap文件)中解析協議請求並生成一個.fuzzer
  • Metabarcoding workshop 課前準備
    歡迎大家報名參加這個月底(1月30-31日)在版納植物園舉辦的Metabarcoding workshop(詳情請點擊文末「閱讀原文」)。這次培訓我們將使用瑞典農業大學的生物信息課程中Metabarcoding部分的材料(https://sgbc.github.io/course/tutorials/docs/16S/),帶領大家一步一步的學習。
  • websocket-fuzzer : WebSocket Fuzz 測試工具;Bash讀取/etc/passwd技巧
    參考連結:https://linux-audit.com/elf-binaries-on-linux-understanding-and-analysis/4.逆向工程學習課程參考連結:https://www.exploit-db.com/exploits/44750/1.websocket-fuzzer
  • PTFuzzer:一個基於硬體加速的二進位程序Fuzz工具
    使用方法3.1 從github上下載源碼git clone https://github.com/hunter-ht-2018/ptfuzzer.git3.2 編譯安裝編譯之前先安裝依賴項,ptfuzzer依賴的程序和庫包括python,capstone,cle以及msr-tools。在源碼目錄下有個check_dep.sh腳本,可以直接運行安裝這些依賴項。
  • 起風了,華為雲服務workshop
    這次workshop我們為什麼選擇雲服務?1)快速學習和吸取全面了解各方信息,快速拓寬思考的維度和深度,讓設計和思考更完善和深入。以本次workshop設計的目的來統領討論的方向與進度,並不斷記錄與歸納討論結果,適時調整討論話題。3)快速動工和及時補充。
  • 技術部 | VSCode Workshop 預告及準備事宜
    現在各位機導選手都已經結束了matlab,開始C和 C++的學習,與matlab只能使用官方IDE不同的是,C和C++有很多種編輯方式,可以使用各種第三方的編輯器和IDE進行項目開發,在lab中大部分同學都已經安裝了Clion作為IDE,我們將在這次workshop中介紹被很多開發者稱為「宇宙第一編輯器」的VSCode,幫助你們更好地使用這個優秀的代碼編輯器
  • Team Trainer Workshop 團隊培訓師培訓
    L&D conducted 「Team Trainer」 workshop two days on Dec 27th,2016 &
  • CVPR 2020 Workshop on Continual learning in Computer Vision 徵稿
    我們的workshop (研討會)主要關注計算機視覺問題中的Continul learning的能力。在計算機視覺系統中,如何在多個連續任務中保證算法的穩定性,如何更加有效的遷移過去以及當前的知識來提高各項任務的表現。研討會主要分為研討會文章收錄,現場專家報告,Continual learning in Computer Vision挑戰賽。
  • 工作坊:Arduino與Processing編程 Workshop: Arduino & Processing
    上周日,我們學習了Arduino與傳感器,有學員很好奇這些代碼都是什麼意思,很想學習自己寫代碼,而不是複製和簡單修改別人的代碼。
  • 第一性原理計算學習資料連結大全(2020年4月更新)
    AMCSD – American Mineralogist Crystal Structure Database,美國礦物質晶體結構資料庫:http://rruff.geo.arizona.edu/AMS/amcsd.php,(6)AFLOW:http://www.aflowlib.org
  • FastAPI 學習之路(十六)Form表單
    FastAPI 系列文章:             FastAPI 學習之路(一)             FastAPI
  • Recipe | Stuffed Cabbage (w/ cooking workshop info)
    In the live video workshop, I will make a few dishes to demonstrate1) how to enhance flavor naturally;2) hands-on kitchen skills;3
  • 探索 libp2p:基本知識
    當然,這樣「白嫖」用戶的帶寬是有代價的,用戶間傳輸的數據很難被「監控」到(如果每個消息都要往伺服器轉發一份,那 p2p 也沒有多大意義了),於是相關的服務也無法根據這些數據去做各種各樣的深度學習和分析,進而更好地提升用(zhuan4)戶(qian2)體(neng2)驗(li4)。所以隨著網際網路廠商越來越不差錢,對 p2p 技術的渴求也沒那麼強烈了。
  • libVLC 事件機制
    libvlc_media_list_event_manager():媒體列表相關的事件管理器。比如,要監聽媒體列錶停止(libvlc_MediaListPlayerStopped),就需要用到它。   2.