A Suture é uma ferramenta de análise de mancha estática e de alta ordem (para C) com base no LLVM IR e construída no Dr. Checker (muito obrigado a seus criadores!), Dois destaques são:
Nomeamos sutura na esperança de que possa ser cirurgicamente preciso, enquanto é capaz de costurar várias funções de syscalls/entrada para construir fluxos de dados de alta ordem. Para mais detalhes, consulte o nosso artigo: Descobrindo estaticamente as vulnerabilidades de estilo de alta ordem nos núcleos do sistema operacional no ACM CCS'21 .
Primeiro clone o repo:
~$ git clone https://github.com/seclab-ucr/SUTURE.git suture
Em seguida, configure o ambiente LLVM para sutura:
~$ cd suture
~/suture$ python setup_suture.py -o ../suture_deps
Opções (para setup_suture.py ):
-b Especifica o nome da ramificação do LLVM a ser configurado para sutura, o padrão é "release_90" (ou seja, llvm 9.0) neste repo LLVM Mirror.-o Especifica a pasta para hospedar todas as coisas exigidas pela Suture (por exemplo, LLVM), use qualquer pasta que preferir. Dependendo do seu hardware, a configuração do LLVM pode levar algum tempo. Após o término, um arquivo SRCIPT chamado env.sh será gerado sob a pasta raiz da sutura, ele contém comandos para definir as variáveis de ambiente usadas pela sutura.
IMPORTANTE : Certifique -se de ativar esse env.sh todas as vezes antes de criar/usar a sutura (você também pode adicionar seus comandos contidos ao .BashRC para ativação automática no login do shell)!
~/suture$ source env.sh
Em seguida, vamos construir sutura:
~/suture$ cd llvm_analysis
~/suture/llvm_analysis$ ./build.sh
Após uma construção bem -sucedida, a sutura está pronta para uso.
A sutura pode ser usada para descobrir vulnerabilidades em mancha de alta ordem pronta para uso, nesta seção, passamos por esse processo com um exemplo (por exemplo, o exemplo motivador, como mostrado na seção 2.1 em nosso artigo).
Para descobrir a sutura de vulnerabilidades, requer dois tipos de entrada: (1) um módulo de programa compilado ao LLVM Bitcode (por exemplo, um arquivo .bc ) e (2) um arquivo de configuração para o módulo que manifesta suas funções de entrada e argumentos controlados pelo usuário.
Vamos primeiro preparar o LLVM Bitcode para o nosso exemplo motivador:
~/suture$ cd benchmark
~/suture/benchmark$ ./gen.sh motivating_example
NOTA : Por conveniência, fornecemos gen.sh para compilar um .C a .bc e .ll (Human LLVM Bitcode LLVM), com o nível de otimização -O1 .
Agora, devemos ter o Motivating_Example.bc na mesma pasta de referência , esse é o módulo do programa de entrada para a sutura.
Em seguida, ele vem ao arquivo de configuração, já preparamos um para o exemplo motivador:
~/suture/benchmark$ cat conf_motivating_example
entry0 MY_IOCTL 1
entry1 NULL_ARG
entry2 NULL_ARG
Explicação : Cada linha no arquivo de configuração descreve as informações de uma função de entrada (por exemplo, a função de nível superior sem chamadas e geralmente serve como interface externa) no módulo do programa, contém até 3 tokens espaciais:
entry_x MY_IOCTL 0_2 .Assim, como mostrado acima, conf_motivating_example especifica que existem três funções de entrada em motivação_example.c : Entry0 (o parâmetro 1 é controlado pelo usuário), Entry1 e Entry2 (sem parâmetros controlados pelo usuário). Isso corresponde ao código do exemplo motivador.
Depois que o Bitcode do programa e o arquivo de configuração de entrada estão prontos, podemos executar a sutura para descobrir vulnerabilidades em maiúsculas:
~/suture$ ./run_nohup.sh benchmark/motivating_example.bc benchmark/conf_motivating_example
Explicação : run_nohup.sh é um script simples que invocava os passes de análise de sutura LLVM compilados:
~/suture$ ./run_nohup.sh [path/to/program.bc] [path/to/entry_func_config]
Uma vez iniciado, dependendo do hardware real e da complexidade do programa de destino, o tempo necessário para a sutura concluir a descoberta de análise e vulnerabilidade varia muito. Um programa simples como o nosso exemplo motivador geralmente termina instantaneamente.
Decida se a análise termina : durante a execução, a sutura continua fazendo login em um arquivo no mesmo diretório do arquivo de configuração de entrada , suponha que o caminho do arquivo de configuração seja /path/to/conf_program , o arquivo de log será /path/to/conf_program.log . Podemos decidir se a análise termina monitorando o log:
~/suture$ grep "Bug Detection Phase finished" benchmark/conf_motivating_example.log
O arquivo de log acima mencionado também é a saída da Suture, a sutura incorporará suas possíveis vulnerabilidades descobertas no arquivo de log, que podem ser extraídas e organizadas em um relatório de aviso final após o término da análise:
~/suture$ ./ext_warns.sh benchmark/conf_motivating_example.log
Explicação : Ext_warns.sh extrairá todos os avisos (em JSON) incorporados no arquivo de log fornecido, reorganizará e prenderá-los nos relatórios finais de aviso. Os relatórios de aviso serão colocados em uma pasta no mesmo caminho do arquivo de log, suponha que o arquivo de log seja /pathy/to/conf_program.log , a pasta de relatório de aviso será /path/to/warns-conf_program-yyyyy-mm-dd .
~/suture$ ls benchmark/warns-conf_motivating_example-2021-11-10/
all int_overflow taint_data_use taint_loopbound taint_ptr_def
Na pasta, existem 5 relatórios de aviso, todos contêm todos os tipos de avisos agrupados de acordo com sua relação de fluxo de dados, enquanto outros relatórios contêm apenas tipos específicos de avisos (por exemplo, transbordamento inteiro, desreferência de ponteiro contaminado, etc.), mais detalhes sobre como agruparmos avisos podem ser encontrados em nosso artigo (seção 4). Geralmente usamos apenas o relatório unificado tudo .
~/suture$ cat -n benchmark/warns-conf_motivating_example-2021-11-10/all
1 =========================GROUP 0 (2 - 2)=========================
2 #####Warned Insts#####
3 (u'motivating_example.c', 30, [u'IntegerOverflowDetector'])
4 ######################
5
6 ++++++++++++++++WARN 0 (2 - 2)++++++++++++++++
7 IntegerOverflowDetector says: motivating_example.c@30 (bar : %add = add i8 %0, -16, !dbg !31)
8 ********Trace 0(2)********
9 #####CTX##### entry0
10 entry0 (motivating_example.c@18)
11 #####INSTS#####
12 >>>>>>>>>>>>>>>>>>tag: 0x55b206570420 tf: 0x55b20695b1b0 (2)>>>>>>>>>>>>>>>>>>
13 motivating_example.c@18 ( %cond = icmp eq i32 %cmd, 0, !dbg !31)
14 motivating_example.c@21 ( store i8 %user_input, i8* getelementptr inbounds (%struct.data, %struct.data* @d, i64 0, i32 1, i64 0), align 4, !dbg !32, !tbaa !34)
15 #####CTX##### entry1 -> bar
16 entry1 (motivating_example.c@35)
17 ----> (motivating_example.c@35 : %call = tail call i32 @bar(i8* bitcast (%struct.data* @d to i8*)), !dbg !27)
18 bar (motivating_example.c@30)
19 #####INSTS#####
20 >>>>>>>>>>>>>>>>>>tag: 0x55b2065e7050 tf: 0x55b2068174f0 (1)>>>>>>>>>>>>>>>>>>
21 motivating_example.c@30 ( %0 = load i8, i8* %add.ptr, align 1, !dbg !31, !tbaa !32)
22 motivating_example.c@30 ( %add = add i8 %0, -16, !dbg !31)
23
Explicação : Em um alto nível, o relatório de aviso contém alguns grupos de aviso, cada grupo contém vários avisos e cada aviso contém vários traços de mancha que se originam de uma entrada do usuário e afundam para uma mesma declaração de programa sensível. Em outras palavras, um aviso é levantado para uma determinada declaração do programa e de um tipo específico (por exemplo, transbordamento inteiro), enquanto um grupo contém vários avisos intimamente relacionados da perspectiva do fluxo de dados (consulte a Seção 4 em nosso artigo), portanto, um grupo pode ter várias declarações de programa avisadas e incluir diferentes tipos de aviso.
Pegue o relatório de aviso acima como um exemplo:
===GROUP No. (min_order - max_order)=== , onde min_order é a ordem mínima (por exemplo, simplesmente colocar, a ordem é a hora da função de entrada invocações necessárias na max_order de manchas, consulte a seção 3.2 em nosso artigo para uma definição mais formal.) De trituras pequenas entre os grupos.WARN No. é local de seu grupo.***Trace No. (order)*** , um traço de mancha sempre tem uma ordem única que pode ser calculada (por exemplo, não um intervalo).O relatório de aviso identifica uma vulnerabilidade válida de excedentes inteiros no exemplo motivador, evitando possíveis alarmes falsos que uma ferramenta de análise estática menos precisa pode gerar, os detalhes podem ser encontrados na Seção 2.1 do nosso artigo.
Este repositório também contém algumas outras ferramentas/scripts que você pode achar úteis.
~/suture$ python llvm_analysis/AnalysisHelpers/EntryPointIdentifier/entry_point_identifier.py /path/to/prog_module.bc
~/suture$ cat /path/to/prog_module.bc.all_entries
Explicação : Este script pode identificar algumas funções de entrada comum em um módulo de driver de dispositivo Linux (por exemplo, ioctl () , read () , write () , etc.), o que ajuda a construção do arquivo de configuração de entrada como a entrada para a sutura. Esse utilitário é implementado principalmente pelo Dr. Checker original, com base em alguns conhecimentos do domínio do kernel (por exemplo, estruturas de dados específicas como o File_operations usadas para definir pontos de entrada do driver), então fizemos algumas melhorias (por exemplo, tornam as heurísticas mais robustas).
~/suture$ python flt_warns.py /path/to/warn_report [Regex] > /path/to/filtered_warn_report
Explicação : Por nossa experiência, muitos alarmes falsos no relatório de aviso geralmente compartilham um mesmo trace de sub-dinzal problemático (consulte a Seção 6.3 em nosso artigo). Desde que o revisor de aviso inspecione um alarme falso e reconheça o sub-rastreamento indutor de FP, naturalmente, ela pode tentar excluir automaticamente todos os outros alarmes falsos semelhantes que contêm o mesmo sub-trace, reduzindo a taxa de alarme falso percebido pelo revisor. Para esse fim, fornecemos este simples FLT_WARNS.PY que leva o relatório de aviso original e a expressão regular do Python como entrada, ele combinará com o ER contra todos os traços de mancha no relatório, uma vez correspondidos, o traço em mancha será tratado como um alarme falso. O script gerará um novo relatório de aviso filtrado, excluindo todos os traços de mancha correspondentes.
Você pode estar interessado em usar a sutura como uma análise estática de uso geral (por exemplo, obter o conjunto de pontos para/mancha de uma variável em um determinado local do programa), isso é com certeza factível, mas, diferentemente da descoberta de vulnerabilidades, você precisa sujar as mãos e mergulhar no código de sutura para se familiarizar com as principais estruturas de dados que são usadas e algumas funções importantes. Espero que as dicas a seguir possam ajudar:
O LLVM evolui muito rapidamente, sem uma garantia de compatibilidade com versões anteriores. Muito provavelmente, você encontrará alguns erros de compilação ao tentar criar sutura com uma versão LLVM mais recente (por exemplo,> 9.0). Felizmente, porém, esses erros de compilação geralmente são fáceis de resolver (por exemplo, geralmente o caso que precisamos apenas substituir as APIs LLVM desatualizadas pelos mais novos). Então, basicamente, para adaptar a sutura a uma versão mais recente do LLVM, primeiro precisamos configurar um LLVM mais recente em 0x0 (por exemplo, precisar modificar o Setup_suture.py para clonar llvm repo a partir de uma fonte ativa e especificar o nome mais novo) e, em seguida, tentar construir suturas contra ele, resolvendo o encontro de encontros.