地狱怪客

使用WinAFL Fuzz Hangul(HWP)AppShield

文章原文https://www.sigpwn.io/blog/2018/1/29/using-winafl-to-fuzz-hangul-appshield

 

在POC 2017参加“漏洞发现和分类自动化”培训后,我决定给WinAFL一个韩国文字处理器Hangul(HWP)。我想感谢@richinseattle   帮助我完成WinAFL模糊过程。

我从过去的研究中知道,Hangul已经集成了一个名为HncAppShield的安全模块,在解析和向用户显示文档之前扫描.hwp文件中的恶意负载等。由于这个模块相对比较新,而且不是由Hancom开发的,所以在我看来这是一个很好的模糊目标。

版本#1

要使用WinAFL,你必须找到特定的功能来模糊,因为它通过使目标函数在循环中运行而不重新启动过程来使用持久性模糊模式。在HncAppShield的情况下,它是一个DLL,所以我创建了一个简单的加载器来加载DLL,并通过模糊输入调用AppShield_InspectMalware()。AppShield_InpectMalware()是一个导出的函数,它接收文件路径作为参数。我首先选择了这个函数来模拟WinAFL。


导出HncAppShield的功能

  1. #include <stdio.h>
  2. #include <Windows.h>
  3. #include <iostream>
  4. extern "C" __declspec(dllexport) int fuzz_hwp(wchar_t* hwp);
  5. typedef int(*INSPECT)(wchar_t* filename);
  6. INSPECT AppShield_InspectMalware;
  7. wchar_t* charToWChar(const char* text)
  8. {
  9. size_t size = strlen(text) + 1;
  10. wchar_t* wa = (wchar_t*)malloc(sizeof(wchar_t) * size);
  11. mbstowcs(wa, text, size);
  12. return wa;
  13. }
  14. int fuzz_hwp(wchar_t* filename)
  15. {
  16. AppShield_InspectMalware(filename);
  17. return 0;
  18. }
  19. int main(int argc, char** argv)
  20. {
  21. HINSTANCE HncAppShield = LoadLibrary(L"HncAppShield.dll");
  22. int isDetected = 0;
  23. if (HncAppShield) {
  24. AppShield_InspectMalware = (INSPECT)GetProcAddress(HncAppShield, (LPCSTR)1);
  25. isDetected = fuzz_hwp(charToWChar(argv[1]));
  26. }
  27. printf("[Malware result] %d\n", isDetected);
  28. return isDetected;
  29. }

我编译了加载程序(HncAppShieldLoader.exe)并使用以下命令开始使用WinAFL进行模糊处理。

  1. afl-fuzz.exe -i in -o out -D D:\DynamoRIO\bin32 -t 10000 -- -coverage_module AppShieldDLL.dll -fuzz_iterations 5000 -target_module HncAppShieldLoader.exe -target_method fuzz_hwp -nargs 1 -- .\HncAppShieldLoader.exe @@


版本#1运行
WinAFL正在工作,开机后我的桌面上每秒执行约20〜30次执行。

版本#2

加载程序的版本#1正在正确地找到新的边缘,但速度很慢并且一直显示进程推送消息。这个过程微调减慢了模糊过程,就像1 execs / sec。我需要摆脱这一点。


由于轻推,装载机版本#1变慢
起初我并不知道是什么原因造成的,所以理查德给出了一些评论,认为这可能是由于文件或其他资源没有正确发布,我必须更深入地了解模块以避免此问题。所以我开始分析在调用AppInspect_Malware()之后会发生什么,看看模块中是否有其他可以模糊的地方。正如您可以看到的,在AppInspect_Malware()的开头有不必要的操作。


不必要的行为需要被忽略
在实际扫描之前获取临时路径,删除HNC临时文件夹内的文件以及沿途的所有检查都可以忽略。如果您跳过这些操作,模糊会变得更快,并避免轻微问题。我花了一些时间对模块进行逆向工程,找到一个接收HWP文件路径作为参数并足够深的函数,以避免不必要的文件系统交互。然后我偶然发现了一些功能。

该函数接收一个目录路径作为参数,并将目录中的每个文件加载到循环中的内存中。如果文件名包含已知HWP存储类型的名称,则会扫描恶意有效负载。所以我深入了解了实际的扫描逻辑!

但是,我将实际的HWP文件作为模糊输入,但是我发现的新函数需要一个充满存储类型作为其文件名的文件的目录。从我的分析中,我知道这个函数实际上接受了临时路径作为参数。然后,必须有一些我已经跳过的功能,在临时路径中创建这些存储命名文件。我重新访问了所有处理HWP文件路径和临时路径的函数。


将HWP文件解包到流文件中的功能
我发现了一个满足我的要求的确切函数。它接收输入HWP文件名和临时路径作为参数。在调试此功能后,我确认红框中的功能是将给定的HWP文件解压缩到流文件的实际功能。

现在我可以直接从我的加载器调用这个函数来将我选择的任何HWP文件解压到某个目录,并告诉我之前发现的扫描函数来扫描目录。我更新了我的装载机,如下所示。

  1. #include <stdio.h>
  2. #include <Windows.h>
  3. extern "C" __declspec(dllexport) int fuzz_hwp(wchar_t* hwp);
  4. // Function Pointers Definition
  5. typedef int(*OPENSTORAGE)(wchar_t*);
  6. typedef BOOL(*HWP_FILE_CHECK1)(wchar_t*);
  7. typedef int(*HWP_FILE_CHECK2)(wchar_t*);
  8. typedef int(*HWP_DUMP)(wchar_t*, int*);
  9. typedef int(*HWP_DUMP_WORKBOOK)(wchar_t*, int, int*);
  10. typedef int(*SCAN_DIRECTORY)();
  11. typedef int(*DELETE_TEMP_FOLDER)();
  12. HWP_FILE_CHECK1 hwp_file_check1;
  13. HWP_FILE_CHECK2 hwp_file_check2;
  14. HWP_DUMP hwp_dump;
  15. HWP_DUMP_WORKBOOK hwp_dump_workbook;
  16. OPENSTORAGE open_storage;
  17. SCAN_DIRECTORY scan_directory;
  18. DELETE_TEMP_FOLDER delete_temp_folder;
  19. wchar_t* output;
  20. wchar_t* input;
  21. wchar_t* get_filename(wchar_t* name)
  22. {
  23. wchar_t fname[40];
  24. wchar_t ext[10];
  25. wchar_t* res = NULL;
  26. _wsplitpath(name, NULL, NULL, fname, ext);
  27. res = (wchar_t*)malloc((wcslen(fname) + wcslen(ext) + 1) * sizeof(wchar_t));
  28. wcscpy(res, fname);
  29. wcscat(res, ext);
  30. return res;
  31. }
  32. int filter_exception(int code, PEXCEPTION_POINTERS ex) {
  33. printf("Filtering %x\n", code);
  34. return EXCEPTION_EXECUTE_HANDLER;
  35. }
  36. wchar_t* charToWChar(const char* text)
  37. {
  38. size_t size = strlen(text) + 1;
  39. wchar_t* wa = (wchar_t*)malloc(sizeof(wchar_t) * size);
  40. mbstowcs(wa, text, size);
  41. return wa;
  42. }
  43. int dump_storage(wchar_t* filename) {
  44. int flag = 0;
  45. wchar_t destname[MAX_PATH];
  46. wchar_t* destname_p;
  47. wchar_t* n;
  48. wchar_t* filep;
  49. wcscpy(destname, output);
  50. wcscat(destname, L"\\");
  51. n = get_filename(filename);
  52. wcscat(destname, n);
  53. free(n);
  54. destname_p = destname;
  55. printf("[+] Target file : %ls\n", filename);
  56. printf("[+] Destination : %ls\n", destname_p);
  57. __try{
  58. if (open_storage(filename) || hwp_file_check1(filename))
  59. {
  60. printf("[+] Hwp file detected \n");
  61. __asm { MOV EBX, destname_p }
  62. hwp_dump(filename, &flag);
  63. __asm { SUB ESP, 8 }
  64. }
  65. else if (hwp_file_check2(filename))
  66. {
  67. __asm {MOV ECX, destname_p}
  68. hwp_dump_workbook(filename, 0, &flag);
  69. }
  70. else {
  71. printf("[!] Invalid file\n");
  72. return -1;
  73. }
  74. printf("[+] Dumped %ls \n", filename);
  75. }
  76. __except (filter_exception(GetExceptionCode(), GetExceptionInformation())) {
  77. printf("[!] Exception Raised While Dumping : %ls\n", filename);
  78. }
  79. }
  80. int fuzz_hwp(wchar_t* filename)
  81. {
  82. int res = 0;
  83. dump_storage(filename);
  84. __asm { MOV ECX, output }
  85. res = scan_directory();
  86. __asm { MOV EDI, output }
  87. delete_temp_folder();
  88. return res;
  89. }
  90. int main(int argc, char** argv)
  91. {
  92. int res = 0;
  93. printf("[-] argv[1] : %s\n", argv[1]);
  94. printf("[-] argv[2] : %s\n", argv[2]);
  95. HINSTANCE HncAppShield = LoadLibrary(L"HncAppShield.dll");
  96. if (HncAppShield) {
  97. hwp_file_check1 = (HWP_FILE_CHECK1)((char*)GetProcAddress(HncAppShield, (LPCSTR)1) + 0x9640);
  98. hwp_file_check2 = (HWP_FILE_CHECK2)((char*)GetProcAddress(HncAppShield, (LPCSTR)1) + 0x1c840);
  99. hwp_dump = (HWP_DUMP)((char*)GetProcAddress(HncAppShield, (LPCSTR)1) + 0xc70);
  100. hwp_dump_workbook = (HWP_DUMP_WORKBOOK)((char*)GetProcAddress(HncAppShield, (LPCSTR)1) + 0xb70);
  101. open_storage = (OPENSTORAGE)((char*)GetProcAddress(HncAppShield, (LPCSTR)1) + 0x9eb0);
  102. scan_directory = (SCAN_DIRECTORY)((char*)GetProcAddress(HncAppShield, (LPCSTR)1) + 0x3b50);
  103. delete_temp_folder = (DELETE_TEMP_FOLDER)((char*)GetProcAddress(HncAppShield, (LPCSTR)1) - 0x300);
  104. }
  105. else {
  106. printf("[!] HncAppShield.dll not found\n");
  107. }
  108. // set input path
  109. input = charToWChar(argv[1]);
  110. printf("[+] Input : %ls\n", input);
  111. // set output path
  112. output = charToWChar(argv[2]);
  113. printf("[+] Output Folder : %ls\n", output);
  114. res = fuzz_hwp(input);
  115. printf("result : %d\n", res);
  116. return 0;
  117. }

新加载器基本上将模糊输入文件解压缩到输出文件夹,对其进行扫描并删除输出文件夹中的文件。

所以在选择了更好的目标函数之后,我重新执行了WinAFL并获得了更好的性能,如下所示。


版本#2以更好的性能运行

最终版本

尽管版本2具有更好的性能,但类似于装载机的版本1,过程推动仍然存在。我需要对目标函数进行最后一次调整才能真正开始模糊运行。

在这之前,我的fuzzing输入是一个HWP文件,它是一个OLE文件。它里面有几个存储和数据流,就像我解释的那样,HncAppShield实际上将HWP文件解压成流并分别扫描它们。我怀疑,也许在解压缩过程中会发生某些事件,这会减慢模糊过程。

我再次开始调试以查看会发生什么,并发现我的一些初始模糊输入HWP文件在解包时会抛出C ++异常。我试图在我的装载器中捕获异常并忽略它们,但是这并没有完全解决这个问题。因此,我可以做的最后一件事就是跳过解包过程并直接在HWP文件中使用fuzz流。

我创建了一个工具,调用HncAppShield中的解包函数来从HWP文件创建流的语料库。我在线下载了大量的HWP文件,并使用我的工具转储流并按存储类型组织它们。现在,我可以通过给流载数据和存储类型作为输入提供加载的内存地址来模糊实际扫描内存中的HWP流的函数(前面讨论的MEM_InspectStorage函数)。WinAFL终于不必在模糊时间解压缩HWP文件了。以下图表高度概述了我们如何达到这一点。请注意,由WinAFL完成的工作量已经减少。


每个装载机版本的模糊目标
所以现在我有一个加载器的最终版本。它将流文件加载到内存并调用内存扫描功能。它的性能高达每秒80人左右,而且没有流程推动。


最终版本运行

  1. afl-fuzz.exe -i BodyText -o out_bodytext -D D:\DynamoRIO\bin32 -t 10000 -- -coverage_module AppShieldDLL.dll -fuzz_iterations 5000 -target_module HncAppShieldLoader.exe -target_method fuzz_storage -nargs 2 -- .\HncAppShieldLoader.exe @@ BodyText
  1. #include <stdio.h>
  2. #include <Windows.h>
  3. #include <iostream>
  4. extern "C" __declspec(dllexport) int fuzz_storage(wchar_t* filename, char* storageType);
  5. extern "C" __declspec(dllexport) int fuzz_hwp(wchar_t* hwp);
  6. typedef int(*INSPECT)(wchar_t* filename);
  7. typedef int(*MEMINSPECT)(void*, int, char*);
  8. INSPECT AppShield_InspectMalware;
  9. MEMINSPECT memory_inspect;
  10. wchar_t* charToWChar(const char* text)
  11. {
  12. size_t size = strlen(text) + 1;
  13. wchar_t* wa = (wchar_t*)malloc(sizeof(wchar_t) * size);
  14. mbstowcs(wa, text, size);
  15. return wa;
  16. }
  17. int fuzz_hwp(wchar_t* filename)
  18. {
  19. AppShield_InspectMalware(filename);
  20. return 0;
  21. }
  22. int fuzz_storage(wchar_t* filename, char* storageType)
  23. {
  24. FILE* input;
  25. int fileSize = 0;
  26. int readBytes = 0;
  27. void* fileContents = 0;
  28. int ret = 0;
  29. if (!_wfopen_s(&input, filename, L"r+b")){
  30. if (input)
  31. {
  32. fseek(input, 0, SEEK_END);
  33. fileSize = ftell(input);
  34. if (fileSize != -1)
  35. {
  36. fseek(input, 0, 0);
  37. fileContents = malloc(fileSize);
  38. if (fileContents)
  39. {
  40. readBytes = fread(fileContents, 1, fileSize, input);
  41. if (readBytes != fileSize){
  42. OutputDebugStringW(L"[!] File read error\n");
  43. }
  44. ret = memory_inspect(fileContents, readBytes, storageType);
  45. }
  46. }
  47. }
  48. free(fileContents);
  49. fclose(input);
  50. }
  51. return ret;
  52. }
  53. int main(int argc, char** argv)
  54. {
  55. HINSTANCE HncAppShield = LoadLibrary(L"HncAppShield.dll");
  56. int isDetected = 0;
  57. if (HncAppShield) {
  58. AppShield_InspectMalware = (INSPECT)GetProcAddress(HncAppShield, (LPCSTR)1);
  59. memory_inspect = (MEMINSPECT)((char*)GetProcAddress(HncAppShield, (LPCSTR)1) + 0x3620);
  60. if (argc == 2) {
  61. isDetected = fuzz_hwp(charToWChar(argv[1]));
  62. }
  63. if (argc == 3) {
  64. isDetected = fuzz_storage(charToWChar(argv[1]), argv[2]);
  65. }
  66. }
  67. printf("[Malware result] %d\n", isDetected);
  68. return isDetected;
  69. }

结论

我立即得到了独特的崩溃和新的边缘,所以我让它运行了大约一个星期。一周后,我有几百个独特的崩溃文件,但其中大多数似乎崩溃在一个相同的功能。使用BugId进行分类,产生一小组独特的崩溃文件。我向KISA(KRCERT)报告了一些问题,目前正在等待他们的正式答复。

HncAppShield主要由内存读取和比较组成,因此我怀疑存在代码执行错误等非常有价值的漏洞。然而,如果与主HWP模块中的漏洞相结合,你们可能能够找到安全检查旁路,这可能是严重的。我希望这篇文章能够帮助那些刚接触WinAFL的人,或者对HncAppShield本身感兴趣的人。

码字很辛苦,转载请注明来自人生在世《使用WinAFL Fuzz Hangul(HWP)AppShield》

评论