Sunday, May 24, 2020

Start fuzzing, Fuzz various image viewers

I will return to writing about what I have done in the past year, it has been 2 years since I came back to the blog. This article I will share the fuzzing experience I have learned through the process of using common fuzzer to find bug in non-source products. The environment I learn is Windows, the fuzzer I usually use is targeted at products on this environment. In this article, I will use a popular fuzzer called Winafl to find errors in popular image viewers such as Irfanview [1], Fast Stone [2], Xnview [3], etc.


Introduction


I will not elaborate on Winafl's architecture, nor how to use it. I will leave some links [4] [5] mentioning these issues at the end of the article, anyone interested can read it.


Why did I choose Image Viewers to approach fuzz? We can say file format fuzzing is a fuzzing direction is almost the most popular today. It takes little time to prepare, accessible, easy to find corpus (depending on the case),... and maybe logic parsing these file formats still have very high errors.


Here I will take an example of Irfanview, how I approach and use fuzzer to find errors parsing Irfanview image formats.


Reverse and understand


We can see Irfanview handles many image file formats. Some formats will be handled in the i_view32.exe program, some other file formats will be handled through plugins deployed through DLLs.


We need to understand how the flow of image files is pushed in. The reverse process to understand that logic is quick or slow, difficult, or easy depending on the complexity of each program. With complex programs, it will take a lot of time.


However in this case I will use DynamoRIO [6] (a tool used to calculate the coverage of the program when it executes) to support my reverse faster.


Using DynamoRIO with IDA's lighthouse plugin [7], we can tell with input how the program will go, what code to execute. Save a lot of time when reversing.


For example, when Irfanview processes a jpeg2000 image format, the command runs drrun.exe to generate file coverage:

drrun.exe -t drcov -- i_view32.exe _00042.j2k

With this command, DynamoRIO will generate a file containing information about the loaded DLLs and coverage of the program and each DLL file.


Here is an example output file coverage:



We can see the JPEG2000.dll DLL is loaded during the processing of _00042.j2k file.


Now use the lighthouse plugin on IDA to see the results. We can see that the commands highlighted in green are the ones that were executed when i_view32.exe processed the _00042.j2k file.



From there we will trace back the jpeg2000 image processing functions, by finding the related strings is the fastest and most effective way I often use. We can see that i_view32.exe will load library JPEG2000.dll and then call ReadJPG2000_W() function to process the jpeg2000 file.


Let's debug to see the parameters passed into the ReadJPG2000_W() function, we set the breakpoint at the address calling the ReadJPG2000_W() function:

According to the status of the stack at the time the ReadJPG2000_W() function is called, the parameters passed to the function are as follows:

- wchar_t *argv1: the name of the jpeg2000 file needs to be processed.

- int argv2: variable store value 0.

- wchar_t *argv3: a memory of size 2048.

- wchar_t *argv4: a memory of size 2048, initialized by the string "None".

- int argv5, argv6: used to save parameters while parsing jpeg2000 file.


It is very simple, it is possible to build a harness that calls this function and pass the above parameters to parsing a jpeg2000 image file. Because these parameters are completely independent from the program i_view32.exe.


Here is the harness that I wrote:



Run harness with input jpeg2000 file and check its coverage on JPEG2000.dll

Great, it works properly with i_view32.exe's jpeg2000 processing stream.

With this harness we will be able to use it for fuzzing. I use corpus on the GitHub repo:

 - openjpeg

 - go-fuzz-corpus

 - and some corpus from previous fuzz projects.

With lighthouse, we can also see coverage of each function in DLL.


Herewith the file I use the coverage of the ReadJPG2000_W function is only about 34%, of course, this figure is not ideal when fuzz, you need to find a corpus to push this number as high as possible.


Use Winafl to fuzz jpeg2000 with the harness I built above:


Looking at the interface Winafl we should be interested in some of the following parameters:

- exec speed: the number of test cases that can be executed on 1s

- stability: this indicator shows stability during fuzzing. When running Winafl there will be a certain number of iterations on that test case, in theory, the iterations on the same test case the coverage value must not be changed. If this value changes, the stability will not be high.

- map density: this parameter shows the coverage of the target when running with the current test case.


These three parameters must be high to be effective when the fuzz is high [4].


For other image file formats, Irfanview is treated the same as jpeg2000. The plugin responsible for parsing image files has the same functions for processing. In addition to fuzz jpeg2000 file format I also tried with other formats such as gif, dicom, djvu, ani, dpx, wbmp, webp, ...


Results: CVE-2019-17241, CVE-2019-17242, CVE-2019-17243, CVE-2019-17244, CVE-2019-17245, CVE-2019-17246, CVE-2019-17247, CVE-2019-17248 , CVE-2019-17249, CVE-2019-17250, CVE-2019-17251, CVE-2019-17252, CVE-2019-17253, CVE-2019-17254, CVE-2019-17255, CVE-2019-17256, CVE -2019-17257, CVE-2019-17258, CVE-2019-17259, CVE-2019-17261, CVE-2019-17262


Tips and Tricks


While using Winafl, I found Winafl to be most stable on Windows 7. Windows 10 is very bad, DynamoRIO has some problems with memory on Windows 10 that leads to fuzzer or crash.


When fuzzing, I recommend turning on the Page Heap for the harness, to better detect out-of-bounds and uninitialized memory errors.


Afl-tmin is a useful tool to help you minimize corpus, which will be very helpful with fuzzer during mutate corpus. However I usually do not use it because it is too slow. I think I will try using the halfempty tool [8] to replace afl-tmin in the future.


Speed ​​up: Harness is used to call the Windows API the less DynamoRIO instrument process faster. In the harness I wrote above, in the main function I use the LoadLibraryA function to load the DLL I need fuzz and my target_offset in the main function, it will greatly reduce the running speed of the fuzzer.


There are many workarounds. There are a few ways that I can read and read [9], changing the offset to start the instrument is quite good, but when using this method I check it when run in debug mode with iterator, my fuzzer is unstable, I Do not know why. Here, I use lief [10] to solve this problem. I will load the library I need to fuzz before executing the main function:


                
And this is the result, after this fuzz target, has been fixed my way:

                

Speed has improved, but this is not the best way because the speed depends on the target that you fuzz not only depends on the harness you build. I use it because on Windows there is one more fuzzer, but later I prefer to use it instead of Winafl, the way my library loads before the main function matches the architecture of that fuzzer. The following article I will mention more about that fuzzer more.


Corpus: Many people who are new to fuzz will often think of the most important and hardest to build a harness. For me, searching for corpus is the most difficult problem. Searching for corpus with high coverage is very rare, and with these corpus people often will not share because it is very valuable with fuzzing.


When finding a large corpus you should use winafl-cmin to reduce the number of test cases down. There will be test cases whose coverage is duplicated or included in other test cases.


Conclusion


This is the first target I use to learn fuzzing. When my bugs were submitted and got CVE, there were some who said that I farmed and was CVE grass. I also don't argue with those people, I just care what I do, what I will learn from it. Continuing this series on fuzzing, I will share how I approach and fuzz out the bugs of VMWare, Microsoft,... based on what I said in this article.


[3] https://www.xnview.com/en/

[4] https://www.apriorit.com/dev-blog/644-reverse-vulnerabilities-software-no-code-dynamic-fuzzing

[5] https://symeonp.github.io/2017/09/17/fuzzing-winafl.html

[6] https://github.com/DynamoRIO/dynamorio

[7] https://github.com/gaasedelen/lighthouse

[8] https://github.com/googleprojectzero/halfempty

[9] https://github.com/googleprojectzero/winafl/issues/4

[10] https://github.com/lief-project/LIEF




Vietnamese version


Tôi sẽ quay lại viết lách về những gì mình làm được trong năm vừa qua, cũng đã 2 năm rồi tôi mới quay lại viết blog. Bài viết này tôi sẽ chia sẽ những kinh nghiệm fuzzing mà tôi đúc kết được qua quá trình sử dụng các fuzzer phổ biến để tìm lỗi trong các sản phẩm không có mã nguồn. Môi trường tôi tìm hiểu là Windows, fuzzer tôi thường sử dụng đều target vào các sản phẩm trên môi trường này. Trong bài viết này tôi sẽ dụng một fuzzer phổ biến là Winafl để tìm các lỗi trong các trình image viewers phổ biến như Irfanview [1], Fast Stone [2], Xnview [3],…


Introduction


Tôi sẽ không trình bày quá kĩ về kiến trúc của Winafl, cũng như cách sử dụng nó. Tôi sẽ để 1 số link [4] [5] đề cập đến những vấn đề này ở cuối bài viết, ai quan tâm có thể đọc thử.


Tại sao tôi lại chọn các trình Image Viewers để tiếp cận fuzz. Có thể nói file format fuzzing là một hướng fuzzing gần như là phổ biến nhất hiện nay. Nó mất ít thời gian để chuẩn bị, dễ tiếp cận, dễ tìm corpus (tùy trường hợp),… và có thể logic parsing các định dạng file này vẫn còn tồn tại lỗi rất cao.


Ở đây tôi sẽ lấy ví dụ về Irfanview, cách tôi tiếp cận, sử dụng fuzzer như thế nào để tìm lỗi parsing các định dạng ảnh của Irfanview.


Reverse and understand


Ta có thể thấy Irfanview xử lý rất nhiều định dạng file ảnh. Một số định dạng sẽ được xử lý trong chương trình i_view32.exe, một số định dạng file khác được xử lý qua các plugin được triển khai qua các DLL.


yyyyy

Ta cần hiểu luồng xử lý các file ảnh được đẩy vào như thế nào. Quá trình reverse để hiểu logic đó nhanh hay chậm, khó hay dễ phụ thuộc vào độ phức tạp của từng chương trình. Với những chương trình phức tạp ta sẽ mất khá nhiều thời gian.


Tuy nhiên trong trường hợp này mình sẽ sử dụng DynamoRIO [6] (1 tool được sử dụng để tính coverage của chương trình khi nó thực thi) để hỗ trợ cho việc reverse của mình nhanh hơn.


Sử dụng DynamoRIO kèm plugin lighthouse [7] của IDA, chúng ta có thể biết được với input như thế chương trình sẽ đi như thế nào, sẽ thực thi những dòng lệnh nào. Tiết kiệm một lượng lớn thời gian khi reverse.


Ta lấy ví dụ khi Irfanview xử lý 1 định dạng ảnh jpeg2000, Lệnh chạy drrun để generate ra file coverage:

drrun.exe -t drcov -- i_view32.exe _00042.j2k

Với lệnh này DynamoRIO sẽ sinh ra 1 file chứa thông tin các DLL được load và coverage của chương trình và từng file DLL đó.

Dưới đây là output file coverage ví dụ:


Ta có thể thấy DLL JPEG2000.dll được load vào trong quá trình xử lý file _00042.j2k.

 

Bây giờ hãy sử dụng plugin lighthouse trên IDA để xem kết quả. Ta có thể thấy các lệnh được bôi xanh là những lệnh đã được thực thi khi i_view32.exe xử lý file _00042.j2k.


Từ đó ta sẽ trace ngược về các hàm xử lý ảnh jpeg2000, bằng việc find các string liên quan là cách hiệu quả và nhanh nhất mà tôi thường dùng. Ta có thể thấy i_view32.exe sẽ load library JPEG2000.dll sau đó gọi hàm ReadJPG2000_W() để xử lý file jpeg2000.



Hãy debug để xem các tham số truyền vào hàm ReadJPG2000_W(), ta đặt breakpoint tại địa chỉ gọi hàm ReadJPG2000_W():



Theo trạng thái của stack vào thời điểm function ReadJPG2000_W() được gọi, thì các tham số truyền vào hàm lần lượt như sau:

- wchar_t *argv1: tên của file jpeg2000 cần được xử lý

- int argv2: biến lưu giá trị 0

- wchar_t *argv3: một vùng nhớ có kích thước 2048

- wchar_t *argv4: một vùng nhớ có kích thước 2048, được khởi tạo bởi chuỗi None

- int argv5, argv6: dùng để lưu các thông số trong khi parsing file jpeg2000.


Nó rất đơn giản, ta hoàn toàn có thể xây dựng 1 harness gọi hàm này và truyền các tham số như trên để parsing 1 file ảnh jpeg2000. Vì các tham số này hoàn toàn độc lập so với chương trình i_view32.exe.

Dưới đây là harness mà tôi viết:



Chạy thử harness với input là file jpeg2000 và kiểm tra coverage của nó trên JPEG2000.dll



Tuyệt với nó hoạt động đúng với luồng xử lý jpeg2000 của i_view32.exe.

 

Với harness này ta sẽ có thể sử dụng để fuzzing. Tôi sử dụng corpus trên các repo github:

- Openjpeg

- go-fuzz-corpus

- và 1 số corpus từ các project tôi fuzz trước đó.

Với lighthouse chúng ta còn có thể xem coverage của từng hàm trong DLL.



Ở đây với file tôi sử dụng coverage của hàm ReadJPG2000_W chỉ đạt khoảng 34%, tất nhiên con số này là không lý tưởng khi fuzz, bạn cần tìm các corpus để đẩy con số này lên càng cao càng tốt.

 

Sử dụng Winafl để fuzz jpeg2000 với harness tôi xây dựng ở trên:



Nhìn vào giao diện Winafl chúng ta nên quan tâm 1 số thông số sau:

- exec speed: số testcase thực thi được trên 1s

- stability: chỉ số này thể hiện độ ổn định trong khi fuzzing. Khi thực hiện chạy Winafl sẽ có 1 số lần lặp nhất định trên testcase đó, về lý thuyết thì các lần lặp trên cùng 1 testcase giá trị coverage không được thay đổi. Nếu giá trị này thay đổi dẫn đến độ ổn định sẽ không cao.

-  map density: thông số này thể hiện coverage của target khi run với testcase hiện tại.

3 thông số này phải cao thì hiệu quả khi fuzz mới cao [4].

 

Đối với các định dạng file ảnh khác, irfanview đều xử lý tương tự như jpeg2000. Các plugin phụ trách parsing các file ảnh đều có các hàm tương tự để xử lý. Ngoài fuzz định dạng file jpeg2000 tôi còn thử với các định dạng khác như: gif, dicom, djvu, ani, dpx, wbmp, webp,…

 

Kết quả: CVE-2019-17241, CVE-2019-17242, CVE-2019-17243, CVE-2019-17244, CVE-2019-17245, CVE-2019-17246, CVE-2019-17247, CVE-2019-17248, CVE-2019-17249, CVE-2019-17250, CVE-2019-17251, CVE-2019-17252, CVE-2019-17253, CVE-2019-17254, CVE-2019-17255, CVE-2019-17256, CVE-2019-17257, CVE-2019-17258, CVE-2019-17259, CVE-2019-17261, CVE-2019-17262

 

Tips and Tricks


Trong quá trình sử dụng Winafl, tôi nhận thấy Winafl chạy ổn định nhất trên Windows 7. Windows 10 nó chạy rất tệ, DynamoRIO gặp 1 số vấn đề với memory trên Windows 10 dẫn đến fuzzer hay bị crash.


Khi fuzzing, tôi khuyên bạn nên bật Page Heap cho harness, để phát hiện tốt hơn các lỗi out-of-bounds và các lỗi uninitialized memory.


Afl-tmin là 1 tool hữu ích giúp bạn minimize corpus, sẽ rất hữu ích với fuzzer trong quá trình mutate corpus. Tuy nhiên tôi thường không sử dụng vì nó quá chậm. Tôi nghĩ tôi sẽ thử sử dụng tool halfempty [8] để thay thế afl-tmin trong tương lai.


Tăng tốc độ chạy: Harness được sử dụng call các api của windows càng ít thì quá trình DynamoRIO instrument càng nhanh. Ở harness tôi viết trên, trong hàm main tôi sử dụng hàm LoadLibraryA để load DLL tôi cần fuzz và target_offset tôi để ở hàm main, nó sẽ giảm tốc độ chạy của fuzzer đi nhiều.

Có nhiều cách giải quyết. Có 1 số cách mà tôi tìm đọc được [9], thay đổi offset bắt đầu instrument cũng khá hay, nhưng khi sử dụng cách này tôi kiểm tra nó khi run ở mode debug với iterator thì fuzzer của tôi chạy không ổn định, tôi không biết lý do tại sao. Ở đây, tôi sử dụng lief [10] để giải quyết vấn đề này. Tôi sẽ load library tôi cần fuzz lên trước khi thực thi hàm main:

 


Và đây là kết quả, sau khi fuzz target này, đã được sửa theo cách của tôi:



Tốc độ có cải thiện, tuy nhiên đây không phải là cách hay nhất vì tốc độ còn phụ thuộc vào target mà bạn fuzz không phải chỉ phụ thuộc vào harness mà bạn xây dựng. Tôi sử dụng nó bởi vì trên Windows còn 1 fuzzer nữa mà sau này tôi ưu tiên sử dụng nó thay vì Winafl, cách load library của tôi trước hàm main phù hợp với kiến trúc của fuzzer đó. Các bài viết sau tôi sẽ đề cập nhiều về fuzzer đó nhiều hơn.

 

Corpus: Nhiều người mới tiếp cận fuzz thường sẽ nghĩ xây dựng harness để fuzz quan trọng và khó nhất. Đối với tôi thì tìm kiếm corpus mới là vấn đề nan giải nhất. Tìm kiếm corpus với coverage cao rất hiếm, và với những corpus này thường mọi người sẽ không chia sẻ vì nó rất có giá trị với fuzzing.


Khi tìm được 1 lượng lớn corpus bạn nên dùng winafl-cmin để giảm số lượng testcase xuống. Sẽ có những testcase mà coverage của nó trùng lặp hoặc đã bao hàm trong testcase khác.

 

Conclusion


Đây là target đầu tiên tôi sử dụng để học fuzzing. Khi các bug của tôi được submit và lấy CVE có 1 số người nói rằng tôi farm và là CVE cỏ. Tôi cũng không tranh luận gì với những người đó, tôi chỉ quan tâm việc tôi làm thì tôi sẽ học được những gì từ đó thôi. Tiếp nối loạt bài về fuzzing này, tôi sẽ chia sẻ cách mà tôi tiếp cận và fuzz ra những bug của VMWare, Microsoft,… dựa trên những thứ tôi đã nói trong bài viết này.



[3] https://www.xnview.com/en/

[4] https://www.apriorit.com/dev-blog/644-reverse-vulnerabilities-software-no-code-dynamic-fuzzing

[5] https://symeonp.github.io/2017/09/17/fuzzing-winafl.html

[6] https://github.com/DynamoRIO/dynamorio

[7] https://github.com/gaasedelen/lighthouse

[8] https://github.com/googleprojectzero/halfempty

[9] https://github.com/googleprojectzero/winafl/issues/4

[10] https://github.com/lief-project/LIEF

No comments:

Post a Comment