diff --git a/.gitignore b/.gitignore index a88827b..1ca8ae5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,8 @@ public/ *.bin *.so +.direnv/ +.envrc +include.mk +themes/ .hugo_build.lock diff --git a/config.toml b/config.toml index 4d794e4..25e0f9c 100644 --- a/config.toml +++ b/config.toml @@ -23,6 +23,7 @@ enableRobotsTXT = false toc = true post_navigation = true mainSections = [ + "ia", "secu_systeme", "conception_formelle", "secu_logicielle", diff --git a/content/ia/1_introduction/index.md b/content/ia/1_introduction/index.md new file mode 100644 index 0000000..7c1ccd3 --- /dev/null +++ b/content/ia/1_introduction/index.md @@ -0,0 +1,80 @@ +--- +title: "IA : introduction" +date: 2023-10-10 +tags: ["IA"] +categories: ["Intelligence artiticielle", "Cours"] +mathjax: true +--- + +En terme de prévisions autour de l'intelligence artificielle, on entend tout et +n'importe quoi et pas depuis hier! Beaucoup de prévision se sont révélée fausse +néanmoins des progrès existent dans la génération d'image ou encore les voitures +autonomes. + +Nous pouvons citer [Herbert Simon][h_simon] (1965) : + +> Machines will be capable, within twenty years, of doing any work a man can do + +Ou encore [Marvin Minsky][m_minsky] (1970) + +> We solved the venerable mind/body problem, explaining how a system composed +> of matter can have the properties of mind + +Mais plusieurs limitation existants encore: + + * Si dans certains domaines spécialisés elle surpasse les capacité humaines, on + est encore loin **d'une véritable IA générale**; + * Des attaques contre les modèles d'apprentissage existent comme l'ajout de + bruit, des **erreurs dans la détection** entrainent pourtant **une détection + confiante**; + * Les limites fondamentales, comme **morales** par exemple : que doit faire + une voiture autonome dans une situation critique. Nous avons aussi citer les + biais. + +[h_simon]: https://fr.wikipedia.org/wiki/Herbert_Simon +[m_minsky]: https://fr.wikipedia.org/wiki/Marvin_Minsky + +## Un peu d'historique + +Tout commence en 1950 par une publication [d'Alan Turing][a_turing] : *Can +machine think?* + +Le terme *intelligence artificielle* est utilise pour la première fois en 1956 +par [John McCarthy][j_mccarthy]. + +En 1959, le MIT lance le projet *Intelligence Artificielle*. En 1997, *Deep +Blue* bat Gary Kasparov aux échecs. À partir de là les choses s'accélèrent : en +2011 *Watson* bat les champions de *Jéopardy!*, en 2015 un programme +informatique jour à des jeux vidéo *Atari*, en 2016 *AlphaGo* bat les campions +de jeu de Go, en 2017 *Libratus* bat des champions de poker. + +Les progrès récents commencent à ouvrir la porte au prévisions faites dès les +années 60 par Minsky et Simon. + +[a_turing]: https://fr.wikipedia.org/wiki/Alan_Turing +[j_mccarthy]: https://fr.wikipedia.org/wiki/John_McCarthy + +## Définir une IA + +Une définissions possible est "*formalisation et une analyse de causalités +d'évènements*". Elle entraine beaucoup de fantasmes. + +Une première approche pourrait être "*discipline de l'informatique dont le but +est de construire des programmes intelligents*". Dans ce cas qu'est-ce qu'un +programme? + +Marvin Minsky de son côté la définie comme: *ce que l'homme ferait moyennant une +certaine intelligence*. Donc relatif à ce que savent faire les hommes. + +Plus pragmatique et pratique, nous pourrions dire "*tous problèmes pour lesquels +il n'existent pas d'algorithme connu ou avec un coût raisonnable*". Cette +définition peut être résumée en "*problème que l'on ne sait pas résoudre +efficacement*", elle est donc à relier avec **la théorie de la complexité** + +Plus adapté, "*l'intelligence artificielle doit proposer des +solutions logicielles permettant aux programmes de raisonner logiquement*". Cette +définition est basée sur les formalismes logiques : + + * Démonstration automatique des théorèmes; + * Utilisation des règles précises d'inférence; + * Qualités issues des mathématiques (preuves, explications). diff --git a/content/secu_systeme/3_compilation_obfiscation_llvm/index.md b/content/secu_systeme/3_compilation_obfiscation_llvm/index.md new file mode 100644 index 0000000..324ff76 --- /dev/null +++ b/content/secu_systeme/3_compilation_obfiscation_llvm/index.md @@ -0,0 +1,337 @@ +--- +title: "Sécurité système : Introduction à la compilation et obfuscation avec llvm" +date: 2023-10-12 +tags: ["LLVM", "assembleur"] +categories: ["Sécurité système", "Cours", "TD"] +mathjax: true +--- + +Le but de ce cours est de comprendre ce qui se passe lors de la création de +binaire. Nous nous concentrerons sur clang / LLVM dans le cadre de la sécurité, +que se soit en défense ou en attaque. + +## compilation en trois étapes + +Il est possible de découper la compilation en 3 étapes avec LLVM: + + 1. Le code source passe par un *frontend* avec l'analyse syntaxique et + sémantique; + 2. Le résultat passe par un *middle-end* qui va procéder aux optimisations; + 3. Et enfin par un *backend* qui va transformer le code optimisé en code + assembleur et réaliser des optimisations spécifique à l'architecture cible. + +L'obfuscation du code se fait au niveau du *middle-end*. + +Dans le cadre de la suite d'outils autour de LLVM nous avons plusieurs +*backends* (clang, flang, RetDec) qui transforme le code en entrée en code +LLVM (*middle-end*) sur lequel seront réalisées les optimisations. Et enfin +le résultat passe dans un compilateur qui le transforme en code binaire (*ARM. +x86_64, Webassembly,...*). + +Nous avons déjà vu cette partie lors +[de l'introcution]({{}}) + +### Vie d'un hello world + +Voyons maintenant ce qu'il se passe dans le cadre du code suivant: + +```c +#include +int main(int argc, char** argv) { + puts("Hello, world!\n"); + return 0; +} +``` +#### l'AST (Frontend) + +Voyons l'AST produit avec `clang` grâce à la commande suivante avec la commande: + +```shell +clang -Xclang -ast-dump -fsyntax-only main.c +``` + +le voici donc: + +```text +`-FunctionDecl 0x56028bd93cc0 line:3:5 main 'int (int, char **)' + |-ParmVarDecl 0x56028bd93b68 col:14 argc 'int' + |-ParmVarDecl 0x56028bd93be8 col:27 argv 'char **' + `-CompoundStmt 0x56028bd93e88 + |-CallExpr 0x56028bd93e00 'int' + | |-ImplicitCastExpr 0x56028bd93de8 'int (*)(const char *)' + | | `-DeclRefExpr 0x56028bd93d70 'int (const char *)' Function 0x56028bd8f730 'puts' 'int (const char *)' + | `-ImplicitCastExpr 0x56028bd93e40 'const char *' + | `-ImplicitCastExpr 0x56028bd93e28 'char *' + | `-StringLiteral 0x56028bd93d90 'char[15]' lvalue "Hello, world!\n" + `-ReturnStmt 0x56028bd93e78 + `-IntegerLiteral 0x56028bd93e58 'int' 0 +``` + +#### Langage intermédiaire (Middle-end) + +Maintenant, regardons le code LLVM produit par clang: + +```shell +clang -S -emit-llvm -Xclang -disable-O0-optnone main.c -o +``` + +À ce niveau, le code obtenu n'est pas optimisé: + +```llvm +; ModuleID = '/home/user/code/main.c' +source_filename = "/home/user/code/main.c" +target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-pc-linux-gnu" +@.str = private unnamed_addr constant [15 x i8] c"Hello, world!\0A\00", align 1 +; Function Attrs: noinline nounwind uwtable +define dso_local i32 @main(i32 noundef %0, ptr noundef %1) #0 { + %3 = alloca i32, align 4 + %4 = alloca i32, align 4 + %5 = alloca ptr, align 8 + store i32 0, ptr %3, align 4 + store i32 %0, ptr %4, align 4 + store ptr %1, ptr %5, align 8 + %6 = call i32 @puts(ptr noundef @.str) + ret i32 0 +} +declare i32 @puts(ptr noundef) #1 +``` +#### Optimisation (Middle-end) + +Voici la commande permettant de lancer les optimisations sur le code +intermediaire LLVM: + + +```shell +opt -S -O2 main.ll +``` + +Le code produit contient plus les élements jugés inutiles comme ici `argv` et +`argc` : + +```text +; ModuleID = '' +source_filename = "/home/user/code/main.c" +target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128" +target triple = "x86_64-pc-linux-gnu" +@.str = private unnamed_addr constant [15 x i8] c"Hello, world!\0A\00", align 1 +; Function Attrs: nofree noinline nounwind uwtable +define dso_local i32 @main(i32 noundef %0, ptr nocapture noundef readnone %1) local_unnamed_addr #0 { + %3 = tail call i32 @puts(ptr noundef nonnull @.str) + ret i32 0 +} +; Function Attrs: nofree nounwind +declare noundef i32 @puts(ptr nocapture noundef readonly) local_unnamed_addr #1 +``` + +#### Transformation en assembleur (Backend) + +Voici la commande qui permet de transformer le code intermédiaire optimisé en +code assembleur: + +```shell +llc main.ll -o main.s +``` + +Le code obtenu sera cette-fois dépendant de l'architecture cible. Une fois +trasformé en code machine, il se sera pas directement exécutable, il manque +l'édition de lien. + +## LLVM + +LLVM signifie *Low Level Virtual Machine* est une spécification d'une +**représentation intermédiaire** (LLVM-IR) accompagnée d'un ensemble d'outils +qui communiquent autour de rette réprésentation. + +LLVM se compose de **modules**, son langage est de type RISC fortement typé et +non signé - certaine opération le sont par contre comme `div` et `sdiv`. + +Prenons comme exemple le code *C* suivant : + + +```c +double polynome(float x) { + return 2*x*x*x + 7*x*x + 9*x + 1234; +} +``` + +Voici la représenation LLVM : + +```llvm +; Function Attrs: mustprogress nofree nosync nounwind readnone willreturn uwtable +define dso_local double @polynome(float noundef %0) local_unnamed_addr #0 { + %2 = fmul float %0, 2.000000e+00 + %3 = fmul float %2, %0 + %4 = fmul float %0, 7.000000e+00 + %5 = fmul float %4, %0 + %6 = tail call float @llvm.fmuladd.f32(float %3, float %0, float %5) + %7 = tail call float @llvm.fmuladd.f32(float %0, float 9.000000e+00, float %6) + %8 = fadd float %7, 1.234000e+03 + %9 = fpext float %8 to double + ret double %9 +``` +### Branchements + +Il n'y a pas de structure de contrôle comme dans les langages de plus haut +niveau, mais certaines instructions permettent des branchements (conditionnels +ou non) comme `br`, `ret`, `switch`, `invoke`, `resume` ... + +Prenons comme exemple : + +```c +void then_(int); +void else_(int); +void if_then_else(int a, int b, int c) { + if(a) then_(b); + else else_(c); +} +``` + le code correspondant en représentation intermediaire: + +```llvm + %4 = icmp eq i32 %0, 0 + br i1 %4, label %6, label %5 + +5: ; preds = %3 + tail call void @then_(i32 noundef %1) #2 + br label %7 + +6: ; preds = %3 + tail call void @else_(i32 noundef %2) #2 +``` + +### Static Single Assignement et PHI-Node + +Les instructions LLVM prennent la forme de *SSA* ce qui signifie principalement +qu'une variable peut être **assignée une seule fois**. C'est ici que les +*PHI-Nodes* entrent en jeu. + +Prenons le code *C* suivant: + +```c +a = 1; +if (v < 10) + a = 2; +b = a; +``` + +Le code *LLVM-IR* coorespondant est le suivant: + +```llvm +a1 = 1; +if (v < 10) + a2 = 2; +b = PHI(a1, a2); +``` + +L'instruction `b = PHI(a1, a2)` permet de faire une *affectation conditionnelle* +de `b`. Le fonctionement de phi est le suivant: + +```llvm +%10 = PHI i32 [valeur, label] [valeur, label] +``` +`PHI` peut faire référence à des variables non déclarées. + +### Memoire + +LLVM-IR dispose de quelques instructions pour l'accès à la mémoire comme `load`, +`store`, `cmpxchg`, + +### Types complexe + +*LLVM-IR* dispose de plusieurs rypes complexe comme: + + * **les vecteurs** sour la forme `<4 x i32>` représentant 4 entiers de 32 bits; + * **les tableaux** sous la forme `i32[10]` + * **les structures** sous la forme `my_struct = type { i32, i32}` + +### Les exceptions + +*LLVM-IR* permet la gestion des exceptions, mais nous n;utiliserons pas ces +mécanismes das le cadre de ce cours. LLVM dispose de fonction intrinsèques pour +la gestion des exceptions préfixée par `llvm.eh`. Toutes les fonctions +disponibles sont référencées [sur cette page][l_eh_exception] + +[l_eh_exception]: https://llvm.org/docs/ExceptionHandling.html#exception-handling-intrinsics + +## L'obfuscation + +L'obfuscation a pour but principal de ralentir au maximum l'opération de reverse +engineering. Il est souvent question de protéger les parties les plus sensibles, +celle contenant des clés de chiffrement, des algorithmes etc. + +Cette protection sera de toutes manières éphemère et elle a un prix, voire même +plusieurs: + + * exécution plus lente + * consommation mémoire alourdie + * binaire plus volumineux + +Il faut alors trouver un compromis. nous allons voir les techniques utilisées. + +### Obfusquer les instructions + +Il est question de remplacer les instructions élémentaires par des équivalent +par exemple: + +```text +A + B == (A & B)<<1 + (A ^ B) +A - B == (A & -B)<<1 + (A ^ -B) +A ^ B == A + B - (A & B)<<1 +``` + +### Prédicat opaques + +Il existe plusiers façon d'opacifier certaines partie du code. Il est pas +exemple possible d'ajouter du **code mort** : une condition toujours vérifiée +mène au code "correct" : + +```c +int predicat = F(x); // F(x) est toujours vrai +if ( predicat ) { + // donc on exécutera toujours le bon code + good_code(); +} +else { + // et ça c'est pour perdre le 'reverser' + useless_insanely_complex_code(); +} +``` + +Il est aussi possible de remplacer certaines fonctions mathematiques par +certaines autres par exemple: + +``` +log2(x) == log10(x) - log10(2) +``` + +Il est aussi possible des constantes opaque, trouver par exemple pi avec la +formule suivante: + +```c +pi = 4 * (1 - 1/3 + 1/5 - 1/7 + ... + 1/N); +``` + +Après suffisement d'itération, la marge d'erreur est en dessous de 0,2. + +### Tester ses obfuscations + +Il est très important de **tester les obfuscations** déjà pour ne pas introduire de +bugs, mais aussi pour vérifier qu'elles survivent aux optimisations. Il est +possible de faires des tests unitaires, des tests par *fuzzing*, test de +reproductibilité. + +Il faut savoir que certaines optimisations effectuées par les compilateurs +peuvent faire penser à des obfuscations. La division etant très couteuses, elle +est remplacée par une série d'opérations plus rapide. + +## Premiers TP + +Il est question ici de modifier le code intermédiaire LLVM en utilisant son API. +Nous allons manipuler l'IR. LLVM est capable de faire de l'instrospection par +exemple: + +```c +// est ce que I est une fonction +isa(I); +```