Outils d'utilisateurs

Outils du Site


csaw2013_quals:reversing:keygenme

Table des matières

Keygenme

Énoncé

Un fichier nous est proposé : keygenme32.elf, un binaire ELF 32bits.

En se connectant à nc 128.238.66.219 14549, on obtient le message suivant :

welcome to the activation server
give me the password for 5c8288821aebecda3650ad0870408bcd
too slow :'-(

Lorsqu'on lance le keygenme, voici l'usage affiché :

  usage: ./keygenme32.elf <username> <token 1> <token 2>

Solution

Le plugin d'IDA HexRays nous aide à reconstituer en C le main lancé au démarrage de l'application. Voici le code de cette fonction :

//----- (08049EE0) --------------------------------------------------------
int __fastcall main(int a)
{
  if (argc <= 3 )
  {
	printusage(argv[0]);
	exit(1);
  }
  
  if (strlen(argv[1]) <= 15 )
  {
	printconstraints(argv[1]);
	exit(1);
  }
  
    // Récupération du token1 et token2
  v22 = strtoul(argv[2], 0, 0);
  v21 = strtoul(argv[3], 0, 0);
  
  std__string__rend(&v12, &v9, v26);
  v24 = &v9;
  std__string__rbegin(&v13);
  
  std__string__string_std__reverse_iterator___gnu_cxx____normal_iterator_char___std__string___(
	(int)&v8,
	(int)&v13,
	(int)&v12,
	(int)&v11);
	
  v25 = (int)"000048202129009E00094a002129003700094a002129007900094a00212900b900005020000040208c1800088C17000C8C1600108c0b00148c0f00008c0e000401094020000ea1000298a02001c89820000e914202579020027288260234882601f17820000fa1000296a02001e89820000f9142024b9020027288260234882601d1702020100020214a000115500010";
  
  instructions = operator new(8376);
  
  // Initialisation de instructions en fonction de l'user
  cpu__cpu(instructions, (int)&v14, (int)&v16, (int)&v17);
  cpu__Execute(instructions);
  
  t6 = cpu__GetT6(instructions);
  t7 = cpu__GetT7(instructions);
  
  if (instructions)
  {
	cpu___cpu(instructions);
	operator delete(instructions);
  }
  
  if ( check(t6, t7, v22, v21) )
  {
    // Good Boy
	v5 = std__operator___std__char_traits_char__(std__cout, "*<:-)");
	std__ostream__operator__(v5, std__endl_char_std__char_traits_char__);
  }
  else
  {
    // Bad boy
	v6 = std__operator___std__char_traits_char__(std__cout, ":-(");
	std__ostream__operator__(v6, std__endl_char_std__char_traits_char__);
  }
  
  return 0;
}

En résumé : Un buffer de 8376 octets est alloué, initialisé en fonction de l'username, puis checké à l'aide des 2 tokens dans la fonction check() que voici :

bool __cdecl check(int t6, int t7, unsigned int token1, unsigned int token2)
{
  return t6 == (token1 ^ 0x31333337)
      && t7 == (
        (unsigned __int8) token2
      	| ((unsigned __int8)                    (token2 >> 24) << 8)
      	| ((unsigned __int8)((unsigned __int16) (token2 & 0xFF00) >> 8) << 16)
      	| ((unsigned __int8)((signed int)       (token2 & 0xFF0000) >> 16) << 24)
	);
}

Résumons : t6 et t7 sont obtenus à partir des fonctions “cpu_GetT6” et “cpu_GetT7” qui font ceci :

//----- (080491EC) --------------------------------------------------------
int __cdecl cpu__GetT6(int instructions)
{
  return *(_DWORD *)(instructions + 56);
}
//----- (080491F8) --------------------------------------------------------
int __cdecl cpu__GetT7(int a1)
{
  return *(_DWORD *)(instructions + 60);
}

t6 et t7 sont donc issues de la variable instructions, elle même issue de l'username.

En résumé, la fonction check essaye de vérifier une correspondance entre 2 sommes calculées à partir de l'username avec les 2 tokens passés en paramètre. L'username est connu, puisque lorsqu'on se connecte sur nc 128.238.66.219 14549, on nous demande de donner les tokens pour un username donné (une chaîne hexa de 16 caractères).

Il faut donc trouver un moyen de trouver les valeurs de token1 et token2.

La solution adoptée a été de laisser l'application calculer t6 et t7 d'elle même, de breakpointer dans check pour récupérer les valeurs t6 et t7 lors de la comparaison, puis d'en déduire les valeurs de token1 et token2.

En effet, si on prête attention à la fonction check(), token1 est égal à (t6 ^ 0x31333337), et token2 est égal à t7 avec ses bits en désordre.

On écrit donc tout simplement un script GDB qui récupère ces valeurs :

file keygenme32.elf
start username_zenksecurity 1111111 2222222
break *0x0804a2a2
break *0x0804a2aa
r
printf "token1=%u\n", $eax
c
printf "token2=%u\n", $eax
c
q

Les breakpoints sont placés lors des deux comparaisons, on affiche eax dont la valeur est égale à t6 et t7.

Une fois les valeurs affichées, on les traite en bash

gdb -x gettoken.gdb > token_log;
token1=`grep "token1=" token_log | sed -e 's/token1=//g'`;
token1=$(($token1 ^ 0x31333337));
token2=`grep "token2=" token_log | sed -e 's/token2=//g'`;
token2=`./token2 $token2`;

Le binaire ”./token2” remet les bits de t7 dans le bon ordre.

Une fois les 2 tokens récupérés, on peut les envoyer au serveur, qui nous demande par la suite de valider à la chaîne 10 usernames.

Le flag est donné une fois les 10 usernames keygenés.

csaw2013_quals/reversing/keygenme.txt · Dernière modification: 2017/04/09 15:33 (modification externe)