
Fuzzer para Drivers do Kernel Linux
Este repositório contém todas as fontes (incluindo scripts de configuração) que você precisa para colocar o difuze em funcionamento.
Ubuntu >= 14.04.5 LTS
Consulte o readme
Conforme explicado em nosso artigo, existem dois componentes principais do difuze: Interface Recovery e Fuzzing Engine
O mecanismo de recuperação de interface é baseado em passes de análise do LLVM. Cada etapa da recuperação de interface é escrita como passes individuais. Siga as instruções abaixo para colocar a Interface Recovery em funcionamento.
Esta etapa cuida da instalação do LLVM e do c2xml:
Primeiro, certifique-se de que você tenha o libxml (necessário para o c2xml):
sudo apt-get install libxml2-dev
sudo pip install lxml
Em seguida, criamos um único script que baixa e compila todas as ferramentas necessárias.
cd helper_scripts
python setup_difuze.py --help
usage: setup_difuze.py [-h] [-b TARGET_BRANCH] [-o OUTPUT_FOLDER]
optional arguments:
-h, --help show this help message and exit
-b TARGET_BRANCH Branch (i.e. version) of the LLVM to setup. Default:
release_38 e.g., release_38
-o OUTPUT_FOLDER Folder where everything needs to be setup.
Exemplo:
python setup_difuze.py -o difuze_deps
Para completar a configuração, você também precisa de modificações na variável de ambiente PATH local. O script de configuração lhe dará as alterações exatas que você precisa fazer.
Isso depende da conclusão bem-sucedida da Configuração. Temos um único script que compila tudo, de nada.
cd InterfaceHandlers
./build.sh
Isso depende da conclusão bem-sucedida da Compilação. Para executar os componentes da Interface Recovery nos drivers do kernel, primeiro precisamos converter os drivers em bitcode LLVM.
Primeiro, precisamos ter um kernel compilável. Isso significa que você deve ser capaz de compilar o kernel usando a configuração de compilação normal, ou seja, make.
Primeiro capturamos a saída do comando make; a partir dessa saída, extraímos o comando de compilação exato.
makebear make <all the options to make>
bear make -j8Isso gerará um arquivo compile_commands.json no diretório atual.
Basta passar V=1 e redirecionar a saída para o arquivo.
Exemplo:
make V=1 O=out ARCH=arm64 > makeout.txt 2>&1
NOTA: NÃO USE MÚLTIPLOS PROCESSOS, ou seja, -j. Executar no modo multiprocessamento irá bagunçar o arquivo de saída, pois múltiplos processos tentam escrever no arquivo de saída.
É isso. Em seguida, na etapa seguinte, nosso script pega o makeout.txt gerado e executa a Interface Recovery em todos os drivers reconhecidos.
Todas as várias etapas da Interface Recovery estão encapsuladas em um único script helper_scripts/run_all.py
Como executar:
cd helper_scripts
python run_all.py --help
usage: run_all.py [-h] [-l LLVM_BC_OUT] [-a CHIPSET_NUM] [-m MAKEOUT]
[-c COMPJSON] [-g COMPILER_NAME] [-n ARCH_NUM] [-o OUT]
[-k KERNEL_SRC_DIR] [-isclang] [-clangp CLANG_PATH]
[-llvmlinkp LLVMLINK_PATH] [-skb] [-skl] [-skp] [-skP]
[-ske] [-skI] [-ski] [-skv] [-skd] [-f IOCTL_FINDER_OUT]
optional arguments:
-h, --help show this help message and exit
-l LLVM_BC_OUT Destination directory where all the generated bitcode
files should be stored.
-a CHIPSET_NUM Chipset number. Valid chipset numbers are:
1(mediatek)|2(qualcomm)|3(huawei)|4(samsung)
-m MAKEOUT Path to the makeout.txt file.
-c COMPJSON Path to the compile_commands_json generated by Bear.
-g COMPILER_NAME Name of the compiler used in the makeout.txt, This is
needed to filter out compilation commands. Ex: aarch64
-linux-android-gcc
-n ARCH_NUM Destination architecture, 32 bit (1) or 64 bit (2).
-o OUT Path to the out folder. This is the folder, which
could be used as output directory during compiling
some kernels.
-k KERNEL_SRC_DIR Base directory of the kernel sources.
-isclang flag to indicate that clang was used to built the
kernel
-clangp CLANG_PATH Absolute path to the clang binary (if not provided,
the one available in the path will be used)
-llvmlinkp LLVMLINK_PATH
Absolute path to the llvm-link binary (if not
provided, the one available in the path will be used)
-skb Skip LLVM Build (default: not skipped).
-skl Skip Dr Linker (default: not skipped).
-skp Skip Parsing Headers (default: not skipped).
-skP Skip Generating Preprocessed files (default: not
skipped).
-ske Skip Entry point identification (default: not
skipped).
-skI Skip Generate Includes (default: not skipped).
-ski Skip IoctlCmdParser run (default: not skipped).
-skv Skip V4L2 ioctl processing (default: not skipped).
-skd Skip Device name finder (default: not skipped).
-f IOCTL_FINDER_OUT Path to the output folder where the ioctl command
finder output should be stored.
O script compila, vincula e executa a Interface Recovery em todos os drivers reconhecidos, portanto pode levar um tempo considerável (45 min - 90 min).
O script acima realiza as seguintes tarefas em modo multiprocessador para usar todos os núcleos da CPU:
Todos os arquivos de bitcode gerados serão colocados na pasta fornecida ao argumento -l.
Esta etapa leva um tempo considerável, dependendo do número de núcleos que você possui.
Portanto, se você já tiver realizado esta etapa, pode pulá-la passando -skb.
Isso realiza a vinculação: percorre todos os arquivos de bitcode, identifica os arquivos de bitcode relacionados que precisam ser vinculados e os vincula (usando llvm-link) em um arquivo de bitcode consolidado (que será armazenado junto ao arquivo de bitcode correspondente).
Semelhante à etapa anterior, você pode pular esta etapa passando -skl.
Esta etapa procura as declarações de ponto de entrada nos arquivos de cabeçalho e armazena sua configuração no arquivo: hdr_file_config.txt no diretório de compilação do LLVM.
Para pular: -skp
Esta etapa identifica todos os pontos de entrada em todos os arquivos de bitcode consolidados dos drivers.
A saída será armazenada no arquivo: entry_point_out.txt no diretório de compilação do LLVM.
Exemplo de conteúdo do arquivo entry_point_out.txt:
IOCTL:msm_lsm_ioctl:/home/difuze/kernels/pixel/msm/sound/soc/msm/qdsp6v2/msm-lsm-client.c:msm_lsm_ioctl.txt:/home/difuze/pixel/llvm_out/sound/soc/msm/qdsp6v2/llvm_link_final/final_to_check.bc
IOCTL:msm_pcm_ioctl:/home/difuze/kernels/pixel/msm/sound/soc/msm/qdsp6v2/msm-pcm-lpa-v2.c:msm_pcm_ioctl.txt:/home/difuze/pixel/llvm_out/sound/soc/msm/qdsp6v2/llvm_link_final/final_to_check.bc
Para pular: -ske
Esta etapa executará o componente principal da Interface Recovery (IoctlCmdParser) em todos os pontos de entrada no arquivo entry_point_out.txt. A saída para cada ponto de entrada será armazenada na pasta fornecida para a opção -f.
Para pular: -ski
Agora, mostraremos um exemplo desde o ponto em que você tem as fontes do kernel até a obtenção dos resultados da Interface Recovery.
Enviamos um kernel mediatek 33.2.A.3.123.tar.bz2. Primeiro, baixe e extraia o arquivo acima.
Digamos que você extraiu o arquivo acima em uma pasta chamada: ~/mediatek_kernel
Instale o Bear e siga os passos abaixo:
cd ~/mediatek_kernel
source ./env.sh
cd kernel-3.18
# the following step may not be needed depending on the kernel
mkdir out
make O=out ARCH=arm64 tubads_defconfig
# generating compile_commands.json
bear make -j8 O=out ARCH=arm64
cd <repo_path>/helper_scripts
python run_all.py -l ~/mediatek_kernel/llvm_bitcode_out -a 1 -c ~/mediatek_kernel/kernel-3.18/compile_commands.json -n 2 -o ~/mediatek_kernel/kernel-3.18/out -k ~/mediatek_kernel/kernel-3.18 -f ~/mediatek_kernel/ioctl_finder_out
O comando acima leva um certo tempo (30 min - 1 hora).
Primeiro, todos os resultados da análise estarão na pasta: ~/mediatek_kernel/ioctl_finder_out (argumento dado à opção -f), para cada ponto de entrada será criado um arquivo .txt, que contém todas as informações sobre a interface recuperada.
Se você está interessado apenas nas informações sobre a interface e não se importa com o resto, recomendamos usar o script parse_interface_output.py. Este script converte a saída louca do passe Interface Recovery em belos arquivos json com um formato limpo e consistente.
cd <repo_path>/helper_scripts
python parse_interface_output.py <ioctl_finder_out_dir> <output_directory_for_json_files>
Aqui <ioctl_finder_out_dir> deve ser o mesmo que a pasta fornecida à opção -f e <output_directory_for_json_files> é a pasta onde os arquivos json devem ser criados.
Você pode usar os arquivos json correspondentes para a recuperação de interface do ioctl correspondente.
-g (apenas se você usar makeout.txt)Para fornecer o valor da opção -g, você precisa saber o nome do binário *-gcc usado para compilar o kernel.
Uma maneira fácil de saber isso é usar grep por gcc no makeout.txt e você verá comandos do compilador a partir dos quais poderá saber o nome do binário *-gcc.
Para o nosso exemplo acima, se você fizer grep gcc makeout.txt para a compilação de exemplo, verá muitas linhas como abaixo:
aarch64-linux-android-gcc -Wp,-MD,fs/jbd2/.transaction.o.d -nostdinc -isystem ...
Portanto, o valor para -g deve ser aarch64-linux-android-gcc.
Se o kernel a ser compilado for de 32 bits, então o binário provavelmente será arm-eabi-gcc
Para chipsets Qualcomm (ou msm), você pode ver *gcc-wrapper.py em vez de *.gcc, nesse caso você deve fornecer o *gcc-wrapper.py.
-aDependendo do tipo de chipset, você precisa fornecer o número correspondente.
-oEste é o caminho da pasta fornecida à opção O= para o comando make durante a compilação do kernel.
Nem todos os kernels precisam de um caminho de saída separado. Você pode compilar o kernel sem fornecer a opção O, caso em que você NÃO DEVE fornecer valor para essa opção ao executar run_all.py.
Para kernels compilados com clang, além das opções acima, especifique as seguintes opções (assumindo que você usou compile_commands.json):
-isclang -clangp <PATH_TO_THE_CLANG_USED_TO_BUILD_THE_KERNEL> -llvmlinkp <PATH_TO_THE_LLVM_LINK (will be in the same folder as clang)>
Antes de começarmos o fuzzing, precisamos processar um pouco a saída com nossos analisadores de qualidade de pesquisa (desculpe).
Eles são encontrados aqui. O script principal a ser executado será run_all.py:
$ python run_all.py --help
usage: run_all.py [-h] -f F -o O [-n {manual,auto,hybrid}] [-m M]
run_all options
optional arguments:
-h, --help show this help message and exit
-f F Filename of the ioctl analysis output OR the entire
output directory created by the system
-o O Output directory to store the results. If this
directory does not exist it will be created
-n {manual,auto,hybrid}
Specify devname options. You can choose manual
(specify every name manually), auto (skip anything that
we don't identify a name for), or hybrid (if we
detected a name, we use it, else we ask the user)
-m M Enable multi-device output most ioctls only have one
applicable device node, but some may have multiple. (0
to disable)
Você vai querer passar -f o diretório de saída da análise ioctl, por exemplo, ~/mediatek_kernel/ioctl_finder_out.
-o é onde você vai armazenar os resultados pós-processados. Serão arquivos XML fáceis de digerir (jpits).
-n especifica o sistema até que ponto você deseja confiar em nossa recuperação de nome de dispositivo.
Se você não quiser fazer nenhum trabalho/caça a nomes, pode especificar auto.
Isso, claro, custa pular qualquer dispositivo para o qual não recuperamos um nome. Se você quiser ser paranóico e não confiar em nenhum de nossos esforços de recuperação (totalmente razoável), pode usar a opção manual para nomear você mesmo cada dispositivo.
hybrid então é uma combinação de ambos – nomearemos o dispositivo para você quando pudermos, e recorreremos a você quando falharmos.
-m Às vezes, ioctls podem corresponder a mais de um dispositivo (isso é comum com ioctls v4l2/subdev, por exemplo). O suporte para isso está habilitado por padrão, mas requer interação do usuário para especificar o número de dispositivos para cada dispositivo. Se isso for muito irritante para você, pode desabilitar o prompt passando -m 0 (assumiremos um único dispositivo para cada ioctl).
Após a execução, você deve ter, em sua pasta de saída, uma pasta para cada ioctl.
MangoFuzz é nosso protótipo simples de fuzzer e é baseado no Peach (especificamente MozPeach).
Não é um fuzzer particularmente sofisticado, mas encontra bugs. Também foi construído para ser facilmente expansível. Há 2 componentes neste fuzzer: o mecanismo de fuzzing e o executor. O executor pode ser encontrado aqui, e o mecanismo de fuzzing pode ser encontrado aqui.
O executor roda no telefone, ouvindo dados que o mecanismo de fuzzing enviará para ele.
Simplesmente compile para a arquitetura do seu telefone, faça adb push para o telefone e execute com a porta na qual deseja que ele ouça!
A interface com o MangoFuzz é bastante simples. Você vai querer um objeto Engine e um objeto Parser, no qual você alimentará seu mecanismo.
A partir daqui, você analisa os jpits com seu Parser e então executa o Engine. Fácil!
Fornecemos alguns scripts de execução simples para você começar.
Para executar contra drivers específicos, você pode usar runner.py em uma das pastas ioctl no diretório de saída (criado pelos nossos scripts de pós-processamento).
Por exemplo, ./runner.py -f honor8/out/chb -num 1000. Isso diz ao MangoFuzz para executar por 1000 iterações contra todos os pares de valor de comando ioctl pertencentes ao ioctl/driver chb.
Se, em vez disso, quisermos executar contra um dispositivo inteiro (telefone), você pode usar dev_runner.py. Por exemplo, ./dev_runner.py -f honor8/out -num 100.
Isso continuará iterando sobre os arquivos de driver, alternando aleatoriamente entre eles por 100 iterações cada.
Note que antes que o mecanismo de fuzzing possa se comunicar com o telefone, você precisará usar ADB para configurar o encaminhamento de porta, por exemplo, adb forward tcp:2022 tcp:2022