Boas xente.
Estes días estiven xogando con libfuzzer, unha ferramenta incorporada no compilador clang que nos permite fuzzear un programa que se compile con clang. O fuzzing é pasarlle datos (pseudo-)aleatorios a un programa e ver se casca.
Para facer isto con libfuzzer, tes que definir no programa unha función chamada
LLVMFuzzerTestOneInput
que acepte un buffer de bytes que libfuzzer invocará en
repetidamente en bucle pasando diferentes datos. A sería algo coma esto:
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
DoSomethingInterestingWithMyAPI(Data, Size);
return 0; // Non-zero return values are reserved for future use.
}
Logo tes que compilar o proxecto con clang indicando as seguintes opcións:
clang -g -O1 -fsanitize=fuzzer,address fuzzer-file.c project.c -o fuzz-project
Deste xeito libfuzzer creará un executable (neste caso fuzz-project
) que nos
permitirá fuzzear a libraría. Ademais, libfuzzer tamén engadirá código para
instrumentar a execución do programa (así libfuzzer é capaz de ver que
camiños do código se seguen en cada execución e crear tests máis eficientes e
que descubran novos camiños).
Se estás interesado en libfuzzer podes botarlle unha ollada aos seguintes tutorias:
Están moi ben, xa que veñen con exemplos xa masticadiños para que practicamente non te teñas que esforzar en compilar os programas obxetivos e te podas centrar en aprender libfuzzer.
E isto lévanos ao tema deste post, xa que no mundo real un ten que compilar os proxectos para fuzzear, e isto pode ser bastante enrevesado se non sabes como facer. Imos ver como incorporar libfuzzer en caso que o proxecto que queres fuzzear se compile usando autotools.
Podes identificar un proxecto que usa autotools porque xeralmente tes que seguir estes pasos para compilalo:
autoreconf -i -f -v
./configure
make
No meu caso quería fuzzear libhtp, unha libraría para parsear o protocolo HTTP de xeito seguro e eficiente. Esta libraría úsase en software como o IDS Suricata para poder detectar ataques informáticos na rede.
Polo tanto, libhtp ten que ser capaz de parsear incluso datos malformados sen romperse, xa que senon isto podería ser usado para un atacante para mandar un paquete malicioso que rebente ferramentas que usen libhtp como Suricata e as deixe fora de servizo.
libfuzzer fai fuzzing executando un so proceso e chamando continuamente á mesma función, isto fai que o fuzzing sexa moito máis rápido que con outras ferramentas como AFL, que executa un proceso distinto por cada proba de fuzzing. Sen embargo isto ten a consequencia de que se o programa fuzzea se rompe por calquera motivo, a execución de libfuzzer finaliza tamén.
En consequencia o seu é usar libfuzzer en programas/librarías que non se poidan permitir romper por ningún motivo, como o caso de libhtp, xa que se consegues que se rompan é que acabas de detectar un fallo que necesita ser arranxado.
Se pola contra, queres fuzzear un software que pode permitirse rebentar con datos malformados, como por exemplo un reproductor de vídeo ou lector de pdf, libfuzzer non sería adecuado, xa que é posible que o fuzzeo se detivese cada dous por tres sen atopar un fallo relevante e a cousa complicaríase enormemente. Neste tipo de casos obterías moito mellor resultado con outro fuzzer como AFL.
Bueno, imos ao tema, o primeiro e clonar libhtp con git:
git clone https://github.com/OISF/libhtp
E seguindo as instruccións incluidos no README, o proxecto pódese compilar con estes comandos:
./autogen.sh
./configure
make
Neste caso sabemos que o proxecto usa autotools porque
autogen.sh
executaautoreconf -i -f -v
e ademais contén ficheirosMakefile.am
econfigure.ac
, característicos de autotools.
Vale, compilalo de xeito normal parece fácil, pero como carallo facemos para
meterlle man a isto e compilalo con clang e libfuzzer? E onde incorporamos a
función LLVMFuzzerTestOneInput
de libfuzzer? Nun arquivo do proxecto? Nun
externo? E que carallo escribimos na función LLVMFuzzerTestOneInput
?
Imos por partes. Primeiro, que temos que escribir en
LLVMFuzzerTestOneInput
? Non é unha pregunta fácil, pois iso depende de cada
proxecto. O caso é que temos que ser capaces de pasarlle á libraría un buffer de
datos e ver se casca. Probablemente isto implica facer algún tipo de
inicialización da libraría e logo chamar a unha función que procese un buffer de
datos. Xa che digo agora que sona máis fácil do que é.
Neste caso fun quen de escribir unha proba baseandome nos ficheiros de
exemplo que atopei no proxecto (na carpeta extras
concretamente). Logo
dun rato vendo como funcionaba todo escribin a seguinte función:
#include <stddef.h>
#include <stdint.h>
#include <htp/htp.h>
#include <htp/htp_list.h>
#include <htp/htp_table.h>
int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
htp_cfg_t *cfg = htp_config_create();
htp_connp_t *connp = htp_connp_create(cfg);
htp_connp_req_data(connp, 0, data, size);
// Release
htp_connp_destroy_all(connp);
htp_config_destroy(cfg);
return 0; // Non-zero return values are reserved for future use.
}
Recorda que toda a memoria reservada na función
LLVMFuzzerTestOneInput
debe ser liberada antes de rematala, senon corremos o risco de deixar un leak de memoria e quedarnos sen ela tras executar a función unhas cantas veces (que é o que fai libfuzzer).
Vale, xa temos unha función para probar a parsear datos, onde a poñemos? Pois o seu
o poñelo nun ficheiro externo á libraría (e non no propio código da libraría
como se me pasou pola cabeza ao principio). Tras moitas probas e erros,
finalmente creei o ficheiro fuzz_htp.c
no directorio root do proxecto.
Agora que xa temos o noso ficheiro de test preparado (insisto en que isto sona máis fácil do que é), toca compilar a libraría con clang e libfuzzer.
O primeiro entón e indicar que queremos usar o compilar clang, para o cal
establecemos o valor clang
na variable de entorno CC
co comando export
CC=clang
(neste caso usamos CC
xa que é un proxecto en C, se fose en C++
teríamos que usar CXX
que indica o compilador de C++).
E segundo indicamos que se use libfuzzer coa variable CFLAGS
, para o cal
executamos export CFLAGS="-g -fsanitize=fuzzer,address"
(no caso de ser un
proxecto en C++, usaríase a variable CXXFLAGS
). Estes parámetros indican que
ao compilar queremos incorporar símbolos debugging (-g
), para que a saída de
erros nos de información de onde se atopan estes no código fonte, e por outra
banda -fsanitize=fuzzer,address
usar, como non, libfuzzer e AddressSanitizer.
AddressSanitizer permite detectar máis errors de lectura e escritura fora dos límites dos buffers, para isto crea unha memoria sombra (shadow memory) que rexistra continuamente o estado da memoria normal e descubre se se accede a zonas que non están inicializadas ou fora dos límites dos buffers.
Bueno, a cousa para compilar quedaría entón do seguinte xeito:
export CC=clang
export CFLAGS="-g -fsanitize=fuzzer,address"
./autogen.sh
./configure
make
Sen embargo, ao executar vemos que nos da un erro:
~/libhtp $ export CC=clang
~/libhtp $ export CFLAGS="-g -fsanitize=fuzzer,address"
~/libhtp $ ./autogen.sh
autoreconf: Entering directory `.'
autoreconf: configure.ac: not using Gettext
autoreconf: running: aclocal --force -I m4
autoreconf: configure.ac: tracing
autoreconf: running: libtoolize --copy --force
libtoolize: putting auxiliary files in '.'.
libtoolize: copying file './ltmain.sh'
libtoolize: putting macros in AC_CONFIG_MACRO_DIRS, 'm4'.
libtoolize: copying file 'm4/libtool.m4'
libtoolize: copying file 'm4/ltoptions.m4'
libtoolize: copying file 'm4/ltsugar.m4'
libtoolize: copying file 'm4/ltversion.m4'
libtoolize: copying file 'm4/lt~obsolete.m4'
autoreconf: running: /usr/bin/autoconf --force
autoreconf: running: /usr/bin/autoheader --force
autoreconf: running: automake --add-missing --copy --force-missing
configure.ac:86: installing './compile'
configure.ac:7: installing './missing'
htp/Makefile.am: installing './depcomp'
autoreconf: Leaving directory `.'
~/projects/libhtp $ ./configure
checking for a BSD-compatible install... /usr/bin/install -c
checking whether build environment is sane... yes
checking for a thread-safe mkdir -p... /usr/bin/mkdir -p
checking for gawk... no
checking for mawk... mawk
checking whether make sets $(MAKE)... yes
checking whether make supports nested variables... yes
checking for gcc... clang
checking whether the C compiler works... no
configure: error: in `/home/user/projects/libhtp':
configure: error: C compiler cannot create executables
See `config.log' for more details
E se lle botamos un ollo a config.log
atopamos o seguinte:
configure:3410: checking whether the C compiler works
configure:3432: clang -g -fsanitize=fuzzer,address -O2 -O2 conftest.c >&5
/usr/bin/ld: /tmp/conftest-859fac.o: in function `main':
/home/user/projects/libhtp/conftest.c:14: multiple definition of `main'; /usr/lib/llvm-10/lib/clang/10.0.0/lib/linux/libclang_rt.fuzzer-x86_64.a(fuzzer.o):(.text.main+0x0): first defined here
/usr/bin/ld: /usr/lib/llvm-10/lib/clang/10.0.0/lib/linux/libclang_rt.fuzzer-x86_64.a(fuzzer.o): in function `main':
(.text.main+0x12): undefined reference to `LLVMFuzzerTestOneInput'
clang: error: linker command failed with exit code 1 (use -v to see invocation)
Vemos que temos dous erros, por unha banda multiple definition of `main'
e
pola outra undefined reference to `LLVMFuzzerTestOneInput'
.
O primeiro erro débese a que por un lado, libfuzzer quere crear un executable coa
función main
e por outro autotools, que tamén ten definida unha función main
nun ficheiro auxiliar.
E o segundo erro, débese a que libfuzzer está a intentar enlazar coa nosa función
LLVMFuzzerTestOneInput
, pero o noso arquivo non se atopa dentro do proceso de
compilación (de momento), así que non a atopa.
Para solucionar estes erros o que temos que facer é separar a compilación en
dúas partes. En primeiro lugar compilamos a libraría e logo o noso fuzzer
enlazando o noso ficheiro fuzz_htp.c
coa libraría.
Para facer isto temos que empezar por indicarlle a libfuzzer que non queremos
que xere o executable de fuzzer de boas a primeiras, inda que sí que queremos
que engada o seu código de instrumentación á libraría xerada (de xeito que poda
saber que camiños de código se van executando en cada caso e poder xerar tests
mellores). Para iso cambiamos o valor da variable CFLAGS
de
-g -fsanitize=fuzzer,address
a -g -fsanitize=fuzzer-no-link,address
.
Quedarían así os comandos para compilar a libraría:
export CC=clang
export CFLAGS="-g -fsanitize=fuzzer-no-link,address"
./autogen.sh
./configure
make
E desta se executamos sí que vai (non o poño aquí que ocupa bastante espazo).
Agora queda enlazar o noso ficheiro de fuzzing coa libraría, pero, onde anda a
libraría? Pois no caso de autotools, este as esconde un pouquiño nun
subdirectorio oculto chamado .libs
, que pode estar na carpeta raíz do proxecto
ou na carpeta de código. En calquera caso, para atopar a libraría podemos
executar un find
:
~/libhtp $ find . -name *.a
./htp/lzma/.libs/liblzma-c.a
./htp/.libs/libhtp-c.a
./htp/.libs/libhtp.a
E velaí temos as librarías xeradas no proceso de compilación. Neste caso
interésanos libhtp.a
. Para xerar o binary de fuzzing executamos:
clang -g -fsanitize=fuzzer,address fuzz_htp.c -I . htp/.libs/libhtp.a -lz -o fuzz-htp
Neste comando indicamos que queremos xerar o binario de fuzzing que invoca á
nosa función en fuzz_htp.c
, linkamos a libraría htp/.libs/libhtp.a
e
indicamos que queremos usar como arquivos de cabeceiras (os .h
) os que están
contidos neste proxecto con -I .
(-lz
indícase para linkar un libraría de
compresión que require o proxecto).
Finalmente executamos ./fuzz-htp
e nada, a ver se estoupa a libraría.
~/libhtp$ ./fuzz-htp
INFO: Seed: 2543270220
INFO: Loaded 1 modules (3551 inline 8-bit counters): 3551 [0x5f8f10, 0x5f9cef),
INFO: Loaded 1 PC tables (3551 PCs): 3551 [0x5f9cf0,0x607ae0),
INFO: -max_len is not provided; libFuzzer will not generate inputs larger than 4096 bytes
INFO: A corpus is not provided, starting from an empty corpus
#2 INITED cov: 112 ft: 113 corp: 1/1b exec/s: 0 rss: 31Mb
NEW_FUNC[1/12]: 0x55fcd0 in htp_connp_REQ_FINALIZE /home/user/projects/libhtp/htp/htp_request.c:838
NEW_FUNC[2/12]: 0x5626d0 in htp_connp_REQ_PROTOCOL /home/user/projects/libhtp/htp/htp_request.c:726
#4 NEW cov: 166 ft: 182 corp: 2/5b lim: 4 exec/s: 0 rss: 33Mb L: 4/4 MS: 2 ShuffleBytes-CrossOver-
#6 NEW cov: 166 ft: 186 corp: 3/9b lim: 4 exec/s: 0 rss: 33Mb L: 4/4 MS: 2 CopyPart-ChangeBinInt-
#8 NEW cov: 166 ft: 196 corp: 4/11b lim: 4 exec/s: 0 rss: 33Mb L: 2/4 MS: 2 ShuffleBytes-CopyPart-
#13 NEW cov: 166 ft: 197 corp: 5/13b lim: 4 exec/s: 0 rss: 33Mb L: 2/4 MS: 5 EraseBytes-ChangeByte-ChangeByte-ChangeByte-InsertByte-
#20 NEW cov: 166 ft: 199 corp: 6/16b lim: 4 exec/s: 0 rss: 33Mb L: 3/4 MS: 2 ChangeByte-InsertByte-
NEW_FUNC[1/4]: 0x55ee90 in htp_connp_req_receiver_finalize_clear /home/user/projects/libhtp/htp/htp_request.c:131
NEW_FUNC[2/4]: 0x5634c0 in htp_connp_REQ_IGNORE_DATA_AFTER_HTTP_0_9 /home/user/projects/libhtp/htp/htp_request.c:910
#47 NEW cov: 184 ft: 218 corp: 7/20b lim: 4 exec/s: 0 rss: 33Mb L: 4/4 MS: 2 ChangeBinInt-CrossOver-
#52 NEW cov: 191 ft: 229 corp: 8/24b lim: 4 exec/s: 0 rss: 33Mb L: 4/4 MS: 5 CrossOver-ChangeBit-EraseBytes-ShuffleBytes-CrossOver-
#53 NEW cov: 191 ft: 231 corp: 9/28b lim: 4 exec/s: 0 rss: 33Mb L: 4/4 MS: 1 CopyPart-
#56 NEW cov: 191 ft: 232 corp: 10/30b lim: 4 exec/s: 0 rss: 33Mb L: 2/4 MS: 3 ChangeBit-ShuffleBytes-EraseBytes-
#83 NEW cov: 192 ft: 233 corp: 11/34b lim: 4 exec/s: 0 rss: 33Mb L: 4/4 MS: 2 CopyPart-ChangeBit-
O seu sería pasarlle uns casos de probas iniciais para que libfuzzer poda ter unha orientación de que probar, pero bueno, iso xa o veremos noutro post se cadra.
A pasalo ben e feliz fuzzing.