SIGSEGV V2 Quals: Efficiency (reverse)

Il manque certainement pas mal de choses sur ce write - up un peu brouillon (*un peu beaucoup désolé*), j'essaierai de le mettre à jour! ![](uploads/4d23d438c3784cab72c897cbbdeaeae7/file.png) ![](uploads/4d23d438c3784cab72c897cbbdeaeae7/main.png) Rien d'impressionnant pour le moment, le programme imprime `Please enter the password:` sur la sortie standard puis lis notre mot de passe sur l'entrée standard afin de le stocker dans une variable locale, pour ensuite le passer en argument à la fonction `sub_138`. Notre fonction `main` ressemble probablement à: ```c #include #include int main(void) { char password[21] = { 0 }; puts("Please enter the password:"); read(STDOUT_FILENO, password, 20); return (sub_138b(password)); } ``` `int sub_138b(char *password);` Cette fonction se réserve 424 octets de variables locale, parmi lesquels y figurera une copie de notre mot de passe, et un compteur initialisé à 0 utilisé pour la boucle qui suit. ![](uploads/4d23d438c3784cab72c897cbbdeaeae7/prologue.png) Cette boucle qui suit copie notre mot de passe par blocs de 4 octets (tout en inversant l'ordre des octets par le biais de la fonction `sub_1355`) , dans une variable globale à partir de `0x4070`, tant que le compteur est inférieur ou égal à 4. ![](uploads/4d23d438c3784cab72c897cbbdeaeae7/loop.png) ```c int counter = 0, offset = 0; uint32_t value; uint32_t *dest = NULL; while (i <= 4) { offset = (counter * 4); value = *(uint32_t*)(password + offset); // Récupère 4 octets du mot de pase value = sub_1355(value); // Inverse l'ordre des octets offset = (counter * 4) + 16; dest = 0x4060 + offset; *dest = value; counter++; } ``` Une fois que notre mot de passe est copié, des données issus d'une variable globale sont à leur tour copié dans une variable locale. En effet, l'instruction `rep` effectue `movsq qword`, tant que le registre `ECX = 48` ne vaut pas 0, ce qui signifie que l'on copie 384 octets. ![](uploads/4d23d438c3784cab72c897cbbdeaeae7/loop_after.png) Et paf ! encore une boucle ! Mais bien plus imposante. ![](uploads/4d23d438c3784cab72c897cbbdeaeae7/routes.png) On récupère 12 octets, **des données récemment copiés**, à l'aide d'un nombre entier représenté sur 4 octets (issu d'une variable globale). On répète la même chose, avec les 12 octets suivants à chaque tour de boucle. ![](uploads/4d23d438c3784cab72c897cbbdeaeae7/end_loop.png) Par la suite, on remarque plusieurs **instructions de saut conditionnel**; prenant effet lorsque la comparaison qui les précède révèle une égalité entre nos **4 premiers octets et une constante.** Ces sauts menant à des blocs d'instructions distincts des autres : nous pouvons imaginer que ces 4 octets correspondent à une suite de "saut à faire" afin de dissimuler la vérification du mot de passe. De tel sorte que: ```c uint32_t data[96] = { 0x789abcde }; uint32_t handler_id, handler_first_arg, handler_second_arg; int32_t i = 0, index = 0; while (1) { index = i * 4; handler_first_arg = data[index + 1]; handler_second_arg = data[index + 2]; handler_id = data[index]; if (handler_id == 0x789abcde) ... // first handler else if (handler_id > 0x789abcde) ... // second handler else if (handler_id == 0x6789abcd) ... // third handler // else if ... ... // n handler i += 3; // To be sure to recover the next 12 bytes each times } ``` Nous allons noter les 12 octets à chaque tour de boucle, afin de déduire où l'on saute. On ouvre le programme avec un débuggeur et on place un point d'arrêt à `0x1445` pour récupérer ces 12 octets à chaque tour! **$1** = 4 premiers octets, **$3** = 4 octets suivants, **$4** = 4 octets restants, **$2** = Adresse saut ``` $1 = 0x23456789, $2 = 0x15a6, $3 = 0x4, $4 = 0x3 $1 = 0x6789abcd, $2 = 0x16b9, $3 = 0x4, $4 = 0 $1 = 0x23456789, $2 = 0x15a6, $3 = 0x5, $4 = 0x10001 $1 = 0x23456789, $2 = 0x15a6, $3 = 0x100, $4 = 0x124d34d $1 = 0x23456789, $2 = 0x15a6, $3 = 0x200, $4 = 0x14c12327 $1 = 0x23456789, $2 = 0x15a6, $3 = 0x101, $4 = 0x2fc15955 $1 = 0x23456789, $2 = 0x15a6, $3 = 0x201, $4 = 0x33adb8a3 $1 = 0x23456789, $2 = 0x15a6, $3 = 0x102, $4 = 0x160248e5 $1 = 0x23456789, $2 = 0x15a6, $3 = 0x202, $4 = 0x1fcb8aa1 $1 = 0x23456789, $2 = 0x15a6, $3 = 0x103, $4 = 0x59719cc $1 = 0x23456789, $2 = 0x15a6, $3 = 0x203, $4 = 0x1522278b $1 = 0x23456789, $2 = 0x15a6, $3 = 0x104, $4 = 0xba25af2 $1 = 0x23456789, $2 = 0x15a6, $3 = 0x204, $4 = 0x19854de7 $1 = 0xcdef0123, $2 = 0x1787, $3 = 0x10, $4 = 0x200 $1 = 0x789abcde, $2 = 0x16d1, $3 = 0x10, $4 = 0x100 $1 = 0xdef01234, $2 = 0x16a1, $3 = 0x2, $4 = 0 $1 = 0xbcdef012, $2 = 0x1766, $3 = 0x3ff, $4 = 0 $1 = 0xabcdef01, $2 = 0x17c2, $3 = 0, $4 = 0 ``` Nous pouvons maintenant analyser ce qui se passe aux adresses où l'on saute! ## 15a6: On copie 4 octets Dans ce bloc, nos **4 octets suivants** servent à calculer un multiple de 4 servant de décalage à `0x4060`, pour y placer nos **4 derniers octets**. ![](uploads/4d23d438c3784cab72c897cbbdeaeae7/15a6.png) ## 16b9: /!\ ![](uploads/4d23d438c3784cab72c897cbbdeaeae7/16b9.png) ## /!\ ![](uploads/4d23d438c3784cab72c897cbbdeaeae7/1787.png) ## 16d1: On compare 4 octets! Nous comparons ici les 4 octets que nous avons copier à l'adresse `0x15a6` avec les 4 octets que nous avons hacher à `0x1787`. Si les 4 octets ne correspondent pas, la variable globale à `0x5068` est initialisé avec la valeur 1. Dans le cas contraire, à 0. ![](uploads/4d23d438c3784cab72c897cbbdeaeae7/16d1.png) ## 16a1: Le programme dois continuer? Si la variable globale situé à `0x5068` est à 0. On fait exactement ce qui se passe à l'adresse `0x16b9`. Dans le cas contraire, le prochain tour de boucle récupèrera les 12 octets qui mèneront sans doute à `0x1766`, la fin du programme. ![](uploads/4d23d438c3784cab72c897cbbdeaeae7/16a1.png) ## 1766 et 17c2: Fin du programme! Vous l'avez compris, le programme saute à `0x1766` pour initialisé une variable globale qui sera ensuite utiliser à `0x17c2` comme argument pour la fonction `exit`. ![](uploads/4d23d438c3784cab72c897cbbdeaeae7/1766.png) ![](uploads/4d23d438c3784cab72c897cbbdeaeae7/17c2.png) # Ma solution du challenge En résumé, on copie `27 23 c1 14 a3 b8 ad 33 a1 8a cb 1f f2 5a a2 0b` à l'adresse `0x4260`, et `55 59 c1 2f e5 48 02 16 cc 19 97 05 e7 5d 85 19` à `0x4161` à l'aide des instructions situés à `0x15a6`. De tel sorte qu'on a: ```c static const int32_t flag[] = { 0x124d34d, 0x2fc15955, 0x160248e5, 0x59719cc, 0xba25af2 }; static const int32_t secret[] = { 0x14c12327, 0x33adb8a3, 0x1fcb8aa1, 0x1522278b, 0x19854de7 }; ``` Ensuite le programme fait un petit calcul entre `password[i];` et `secret[i];` à `0x16b9`, puis compare le résultat avec `flag[i];` à `0x16d1`. PAS élégant mais efficace *mdr*, j'ai opter pour un bruteforce. (*me voila à jamais banni de SIGSEGV*). ![](uploads/4d23d438c3784cab72c897cbbdeaeae7/3cwfb7.png) ```c #include #include #include static const char charset[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ{}0123456789_!"; static const size_t charset_len = strlen(charset); static const int32_t result[] = { 0x124d34d, 0x2fc15955, 0x160248e5, 0x59719cc, 0xba25af2 }; static const int32_t salt[] = { 0x14c12327, 0x33adb8a3, 0x1fcb8aa1, 0x1522278b, 0x19854de7 }; static int32_t do_anything(int32_t value, int32_t salt) { int32_t i = 65537; int32_t result = 1; int64_t tmp; while (i) { tmp = i; tmp &= 1; if (tmp) { tmp = (result * value); result = (tmp % salt); } i >>= 1; tmp = (value * value); value = (tmp % salt); } return result; } static int32_t bruteforce(int32_t salt, int32_t result) { int8_t pass[4] = { 0 }; char c; for (int i = 0; i < charset_len; i++) for (int j = 0; j < charset_len; j++) for (int k = 0; k < charset_len; k++) for (int l = 0; l < charset_len; l++) { pass[0] = charset[i]; pass[1] = charset[j]; pass[2] = charset[k]; pass[3] = charset[l]; if (do_anything(*(uint32_t*)pass, salt) == result) { for (i = 0; i < 2; i++) { c = pass[i]; pass[i] = pass[3 - i]; pass[3 - i] = c; } return *(int32_t*)pass; } } return 0; } int main(void) { char password[21] = { 0 }; uint32_t res; for (int i = 0; i <= 4; i++) { res = bruteforce(salt[i], result[i]); memcpy(password + (i * 4), &res, 4); } printf("Le drapeau est %s\n", password); return (0); } ``` Le drapeau est `sigsegv{VM3d_stuff!}` !