Parfois, il est utile de masquer une chaîne d'un fichier binaire (exécutable). Par exemple, il est logique de masquer les clés de chiffrement des fichiers binaires.
Quand je dis "cacher", je veux dire rendre les chaînes plus difficiles à trouver dans le binaire compilé.
Par exemple, ce code:
const char* encryptionKey = "My strong encryption key";
// Using the key
après la compilation produit un fichier exécutable avec les éléments suivants dans sa section de données:
4D 79 20 73 74 72 6F 6E-67 20 65 6E 63 72 79 70 |My strong encryp|
74 69 6F 6E 20 6B 65 79 |tion key |
Vous pouvez voir que notre chaîne secrète peut être facilement trouvée et/ou modifiée.
Je pourrais cacher la chaîne…
char encryptionKey[30];
int n = 0;
encryptionKey[n++] = 'M';
encryptionKey[n++] = 'y';
encryptionKey[n++] = ' ';
encryptionKey[n++] = 's';
encryptionKey[n++] = 't';
encryptionKey[n++] = 'r';
encryptionKey[n++] = 'o';
encryptionKey[n++] = 'n';
encryptionKey[n++] = 'g';
encryptionKey[n++] = ' ';
encryptionKey[n++] = 'e';
encryptionKey[n++] = 'n';
encryptionKey[n++] = 'c';
encryptionKey[n++] = 'r';
encryptionKey[n++] = 'y';
encryptionKey[n++] = 'p';
encryptionKey[n++] = 't';
encryptionKey[n++] = 'i';
encryptionKey[n++] = 'o';
encryptionKey[n++] = 'n';
encryptionKey[n++] = ' ';
encryptionKey[n++] = 'k';
encryptionKey[n++] = 'e';
encryptionKey[n++] = 'y';
… Mais ce n'est pas une méthode sympa. De meilleures idées?
PS: Je sais que cacher des secrets ne fonctionne pas contre un attaquant déterminé, mais c'est bien mieux que rien…
De plus, je connais le cryptage asymétrique, mais ce n'est pas acceptable dans ce cas. Je refactorise une application existante qui utilise le chiffrement Blowfish et transmet les données chiffrées au serveur (le serveur déchiffre les données avec la même clé).
Je ne peux pas changer l'algorithme de cryptage car je dois fournir une compatibilité descendante. Je ne peux pas même changer la clé de cryptage.
Comme indiqué dans le commentaire de pavium réponse , vous avez deux choix:
Malheureusement, si vous devez recourir à l'incorporation à la fois de la clé et de l'algorithme dans le code, aucun n'est vraiment secret, donc vous vous retrouvez avec l'alternative (beaucoup plus faible) de la sécurité par l'obscurité . En d'autres termes, comme vous l'avez mentionné, vous avez besoin d'un moyen intelligent pour masquer l'un ou les deux dans votre exécutable.
Voici quelques options, mais vous devez vous rappeler que aucune d'entre elles n'est vraiment sécurisée selon les meilleures pratiques cryptographiques, et chacune a ses inconvénients:
printf()
, qui a tendance à avoir des chiffres, des lettres et des signes de ponctuation.int
, char
, etc.), prenez un octet quelque part dans chaque variable après son initialisation (à une valeur non nulle, bien sûr) et avant qu'il ne change.Veuillez nous faire savoir comment vous résolvez le problème!
Edit: Vous avez commenté que vous refactoriez le code existant, donc je suppose que vous ne pouvez pas nécessairement choisir la clé vous-même. Dans ce cas, suivez un processus en 2 étapes: Utilisez l'une des méthodes ci-dessus pour crypter la clé elle-même, puis utilisez la clé that pour décrypter les données des utilisateurs.
Je suis désolé pour la longue réponse.
Vos réponses sont absolument correctes, mais la question était de savoir comment masquer la chaîne et le faire correctement.
Je l'ai fait de cette manière:
#include "HideString.h"
DEFINE_HIDDEN_STRING(EncryptionKey, 0x7f, ('M')('y')(' ')('s')('t')('r')('o')('n')('g')(' ')('e')('n')('c')('r')('y')('p')('t')('i')('o')('n')(' ')('k')('e')('y'))
DEFINE_HIDDEN_STRING(EncryptionKey2, 0x27, ('T')('e')('s')('t'))
int main()
{
std::cout << GetEncryptionKey() << std::endl;
std::cout << GetEncryptionKey2() << std::endl;
return 0;
}
HideString.h:
#include <boost/preprocessor/cat.hpp>
#include <boost/preprocessor/seq/for_each_i.hpp>
#include <boost/preprocessor/seq/enum.hpp>
#define CRYPT_MACRO(r, d, i, elem) ( elem ^ ( d - i ) )
#define DEFINE_HIDDEN_STRING(NAME, SEED, SEQ)\
static const char* BOOST_PP_CAT(Get, NAME)()\
{\
static char data[] = {\
BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_FOR_EACH_I(CRYPT_MACRO, SEED, SEQ)),\
'\0'\
};\
\
static bool isEncrypted = true;\
if ( isEncrypted )\
{\
for (unsigned i = 0; i < ( sizeof(data) / sizeof(data[0]) ) - 1; ++i)\
{\
data[i] = CRYPT_MACRO(_, SEED, i, data[i]);\
}\
\
isEncrypted = false;\
}\
\
return data;\
}
La ligne la plus délicate de HideString.h est:
BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_FOR_EACH_I(CRYPT_MACRO, SEED, SEQ))
Me permet d'expliquer la ligne. Pour le code:
DEFINE_HIDDEN_STRING(EncryptionKey2, 0x27, ('T')('e')('s')('t'))
BOOST_PP_SEQ_FOR_EACH_I (CRYPT_MACRO, SEED, SEQ)
( 'T' ^ ( 0x27 - 0 ) ) ( 'e' ^ ( 0x27 - 1 ) ) ( 's' ^ ( 0x27 - 2 ) ) ( 't' ^ ( 0x27 - 3 ) )
BOOST_PP_SEQ_ENUM (BOOST_PP_SEQ_FOR_EACH_I (CRYPT_MACRO, SEED, SEQ))
'T' ^ ( 0x27 - 0 ), 'e' ^ ( 0x27 - 1 ), 's' ^ ( 0x27 - 2 ), 't' ^ ( 0x27 - 3 )
et enfin,
DEFINE_HIDDEN_STRING(EncryptionKey2, 0x27, ('T')('e')('s')('t'))
static const char* GetEncryptionKey2()
{
static char data[] = {
'T' ^ ( 0x27 - 0 ), 'e' ^ ( 0x27 - 1 ), 's' ^ ( 0x27 - 2 ), 't' ^ ( 0x27 - 3 ),
'\0'
};
static bool isEncrypted = true;
if ( isEncrypted )
{
for (unsigned i = 0; i < ( sizeof(data) / sizeof(data[0]) ) - 1; ++i)
{
data[i] = ( data[i] ^ ( 0x27 - i ) );
}
isEncrypted = false;
}
return data;
}
les données de "Ma clé de chiffrement renforcée" ressemblent à:
0x00B0200C 32 07 5d 0f 0f 08 16 16 10 56 10 1a 10 00 08 2.]......V.....
0x00B0201B 00 1b 07 02 02 4b 01 0c 11 00 00 00 00 00 00 .....K.........
Merci beaucoup pour vos réponses!
Masquer les mots de passe dans votre code est une sécurité par obscurité. C'est nocif car cela vous fait penser que vous avez un certain niveau de protection, alors qu'en fait vous en avez très peu. Si quelque chose mérite d'être sécurisé, cela vaut la peine d'être sécurisé correctement.
PS: Je sais que ça ne marche pas contre un vrai hacker, mais c'est bien mieux que rien ...
En fait, dans de nombreuses situations, rien n'est mieux qu'une sécurité faible. Au moins, vous savez exactement où vous en êtes. Vous n'avez pas besoin d'être un "vrai hacker" pour contourner un mot de passe intégré ...
EDIT: En réponse à ce commentaire:
Je connais les paires de clés, mais ce n'est pas acceptable dans ce cas. Je refactorise une application existante qui utilise le cryptage Blowfish. Les données chiffrées sont transmises au serveur et le serveur déchiffre les données. Je ne peux pas modifier l'algorithme de chiffrement car je dois fournir une compatibilité descendante.
Si vous vous souciez de la sécurité, le maintien de la compatibilité descendante est une très mauvaise raison de vous laisser vulnérable avec des mots de passe intégrés. C'est une BONNE CHOSE de rompre la compatibilité descendante avec un système de sécurité non sécurisé.
C'est comme quand les enfants de la rue découvrent que vous laissez votre clé de porte d'entrée sous le tapis, mais vous continuez à le faire parce que grand-père s'attend à le trouver là-bas.
Votre exemple ne cache pas du tout la chaîne; la chaîne est toujours présentée comme une série de caractères dans la sortie.
Il existe différentes façons de brouiller les chaînes. Il y a le simple chiffre de substitution , ou vous pouvez effectuer une opération mathématique sur chaque caractère (un XOR, par exemple) où le résultat alimente l'opération du caractère suivant, etc., etc.
Le but serait de se retrouver avec des données qui ne ressemblent pas à une chaîne, donc par exemple si vous travaillez dans la plupart des langues occidentales, la plupart de vos valeurs de caractères seront comprises entre 32 et 127 - donc votre objectif serait pour que l'opération les place principalement hors de cette plage, afin qu'ils n'attirent pas l'attention.
C'est aussi sûr que de laisser votre vélo déverrouillé à Amsterdam, aux Pays-Bas, près de la gare centrale. (Clignote, et c'est parti!)
Si vous essayez d'ajouter de la sécurité à votre application, vous êtes voué à l'échec dès le départ car tout schéma de protection échouera. Tout ce que vous pouvez faire est de rendre plus complexe pour un pirate de trouver les informations dont il a besoin. Pourtant, quelques astuces:
*) Assurez-vous que la chaîne est stockée en UTF-16 dans votre binaire.
*) Ajoutez des nombres et des caractères spéciaux à la chaîne.
*) Utilisez un tableau d'entiers 32 bits au lieu d'une chaîne! Convertissez chacun en une chaîne et concaténez-les tous.
*) Utilisez un GUID, stockez-le au format binaire et convertissez-le en une chaîne à utiliser.
Et si vous avez vraiment besoin d'un texte prédéfini, chiffrez-le et stockez la valeur chiffrée dans votre binaire. Déchiffrez-le lors de l'exécution où la clé à déchiffrer est l'une des options que j'ai mentionnées précédemment.
Sachez que les pirates auront tendance à casser votre application par d'autres moyens que celui-ci. Même un expert en cryptographie ne pourra pas garder quelque chose en sécurité. En général, la seule chose qui vous protège est le profit qu'un pirate peut gagner en piratant votre code, comparé au coût de le pirater. (Ces coûts seraient souvent très longs, mais si cela prend une semaine pour pirater votre application et seulement 2 jours pour pirater autre chose, quelque chose d'autre est plus susceptible d'être attaqué.)
L'utilisation d'un GUID est pratique si vous stockez le GUID dans son format binaire, pas son format textuel. A = GUID fait 16 octets de long et peut être généré de manière aléatoire. Il est donc difficile de deviner le GUID qui est utilisé comme mot de passe. Mais si vous avez encore besoin d'envoyer du texte brut sur , un GUID pourrait être converti en une représentation sous forme de chaîne pour être quelque chose comme "3F2504E0-4F89-11D3-9A0C-0305E82C3301". (Ou codé en Base64 comme "7QDBkvCA1 + B9K/U0vrQx1A ==" .) Mais les utilisateurs ne verront pas de texte brut dans le code, juste quelques données apparemment aléatoires. Tous les octets dans un GUID ne sont pas aléatoires, cependant. Il y a un numéro de version caché dans les GUID. Utilisation de a GUID n'est cependant pas la meilleure option à des fins cryptographiques. Il est calculé soit sur la base de votre adresse MAC, soit par un nombre pseudo-aléatoire, ce qui le rend raisonnablement prévisible. Pourtant, il est facile de créer et facile à stocker, à convertir et à utiliser. Créer quelque chose de plus long n'ajoute pas plus de valeur, car un pirate ind d'autres astuces pour casser la sécurité. C'est juste une question sur leur volonté d'investir plus de temps dans l'analyse des binaires.
En général, la chose la plus importante qui protège vos applications est le nombre de personnes intéressées. Si personne ne se soucie de votre application, personne ne prendra la peine de la pirater non plus. Lorsque vous êtes le meilleur produit avec 500 millions d'utilisateurs, votre application est piratée en une heure.
J'étais une fois dans une position tout aussi maladroite. J'avais des données qui devaient être en binaire mais pas en texte brut. Ma solution était de crypter les données en utilisant un schéma très simple qui les faisait ressembler au reste du programme. Je l'ai chiffré en écrivant un programme qui a pris une chaîne, converti tous les caractères en ASCII (complété avec des zéros si nécessaire pour obtenir un nombre à trois chiffres), puis ajouté un chiffre aléatoire au début et fin du code à 3 chiffres. Ainsi, chaque caractère de la chaîne était représenté par 5 caractères (tous les nombres) dans la chaîne chiffrée. J'ai collé cette chaîne dans l'application en tant que constante, puis lorsque j'ai eu besoin d'utiliser la chaîne, J'ai décrypté et stocké le résultat dans une variable juste assez longtemps pour faire ce dont j'avais besoin.
Ainsi, pour utiliser votre exemple, "Ma clé de chiffrement renforcée" devient "207719121310329211541116181145111157110071030703283101101109309926114151216611289116161056811109110470321510787101511213". Ensuite, lorsque vous avez besoin de votre clé de chiffrement, décodez-la mais annulez le processus.
Ce n'est certainement pas à l'épreuve des balles mais je ne visais pas cela.
Pour C, vérifiez ceci: https://github.com/mafonya/c_hide_strings
Pour C++, ceci:
class Alpha : public std::string
{
public:
Alpha(string str)
{
std::string phrase(str.c_str(), str.length());
this->assign(phrase);
}
Alpha c(char c) {
std::string phrase(this->c_str(), this->length());
phrase += c;
this->assign(phrase);
return *this;
}
};
Pour l'utiliser, incluez simplement Alpha et:
Alpha str("");
string myStr = str.c('T').c('e').c('s').c('t');
Donc mystr est "Test" maintenant et la chaîne est cachée de la table des chaînes en binaire.
La technologie de cryptage est suffisamment puissante pour sécuriser des données importantes sans les cacher dans un fichier binaire.
Ou est-ce votre idée d'utiliser un fichier binaire pour dissimuler le fait que quelque chose est caché?
Cela s'appellerait stéganographie .
C'est une application client-serveur! Ne le stockez pas dans le client lui-même, c'est l'endroit où les pirates chercheront évidemment. Au lieu de cela, ajoutez (pour votre nouveau client uniquement) une fonction serveur supplémentaire (via HTTPS) pour récupérer ce mot de passe. Ainsi, ce mot de passe ne doit jamais toucher le disque client.
En prime, il devient beaucoup plus facile de réparer le serveur plus tard. Envoyez simplement à chaque fois un mot de passe différent et limité dans le temps par client. N'oubliez pas d'autoriser des mots de passe plus longs dans votre nouveau client.
Vous pouvez utiliser une bibliothèque c ++ que j'ai développée à cet effet. n autre article qui est beaucoup plus simple à mettre en œuvre, a remporté le meilleur article c ++ de septembre 2017. Pour un moyen plus simple de masquer les chaînes, voir TinyObfuscate .
Si vous stockez la clé de cryptage en sens inverse ("yek noitpyrcne gnorts yM"), puis l'inverse dans votre code (String.Reverse), cela empêcherait une simple recherche dans le binaire du texte de votre clé de cryptage.
Pour réitérer le point soulevé par toutes les autres affiches ici, cependant, cela n'apportera pratiquement rien pour vous en termes de sécurité.
Vous pouvez encoder la chaîne en utilisant un encodage trivial, par exemple xor avec binaire 01010101. Pas de réelle protection bien sûr, mais déjoue l'utilisation d'outils comme string
.
Voici un exemple de ce qu'ils ont expliqué, mais sachez que cela sera tout simplement cassé par quiconque est un "pirate" mais arrêtera les enfants avec un éditeur hexadécimal. L'exemple que j'ai fourni ajoute simplement la valeur 80 et en sous-trace l'index, puis crée à nouveau une chaîne. Si vous envisagez de le stocker dans un fichier binaire, il existe de nombreuses façons de convertir une chaîne en un tableau d'octets [].
Lorsque cela fonctionne dans votre application, je rendrais les "mathématiques" que j'utilisais un peu plus complexes
Pour que ce soit clair, pour ceux qui ne comprennent pas .... Vous chiffrez la chaîne avant de l'enregistrer afin qu'elle ne soit PAS enregistrée en texte clair. Si le texte chiffré ne change jamais, vous n'incluez même pas la fonction de chiffrement dans votre version, vous n'avez que la déchiffrement. Ainsi, lorsque vous souhaitez décrypter la chaîne, vous lisez le fichier, puis décryptez le contenu. Cela signifie que votre chaîne ne sera jamais stockée dans un fichier au format texte brut.
Bien sûr, vous pouvez également avoir la chaîne chiffrée stockée sous forme de chaîne de constantes dans votre application et déchiffrer lorsque vous en avez besoin, choisir ce qui vous convient le mieux en fonction de la taille de la chaîne et de la fréquence à laquelle elle change.
string Encrypted = EncryptMystring("AAbbBb");
string Decrypted = DecryptMystring(Encrypted);
string DecryptMystring(string RawStr)
{
string DecryptedStr = "";
for (int i = 0; i < RawStr.Length; i++)
{
DecryptedStr += (char)((int)RawStr[i] - 80 + i);
}
return DecryptedStr;
}
string EncryptMystring(string RawStr)
{
string EncryptedStr = "";
for (int i = 0; i < RawStr.Length; i++)
{
EncryptedStr += (char)((int)RawStr[i] + 80 - i);
}
return EncryptedStr;
}
créez une fonction qui attribue votre mot de passe à un tableau de caractères statiques et renvoie un pointeur sur cette fonction. Exécutez ensuite cette fonction via un programme d'obscurcissement.
Si le programme fait du bon travail. il devrait être impossible de lire votre mot de passe en texte brut à l'aide d'un éditeur hexadécimal pour examiner le programme binaire. (du moins, pas sans rétro-ingénierie du langage d'assemblage. Cela devrait arrêter tous les kiddies de script armés de "chaînes" ou d'éditeurs hexadécimaux, à l'exception du pirate criminellement fou qui n'a rien de mieux pour perdre son temps.)
Je me demande si après l'avoir d'abord obscurci comme d'autres l'ont mentionné, vous pouvez intégrer votre chaîne dans un bloc d'assemblage pour essayer de la faire ressembler à des instructions. Vous pourriez alors avoir un "if 0" ou "goto just_past_string_Assembly" pour sauter par-dessus le "code" qui cache vraiment votre chaîne. Cela nécessiterait probablement un peu plus de travail pour récupérer la chaîne dans le code (un coût de codage unique), mais cela pourrait s'avérer un peu plus obscur.
Je suggère m4.
Stockez votre chaîne avec des macros comme const string sPassword = _ENCRYPT("real password");
Avant la génération, développez les macros en chaîne chiffrée avec m4, pour que votre code ressemble à const string sPassword = "encrypted string";
Déchiffrer dans l'environnement d'exécution.
Je pense que vous voulez que cela ressemble à des instructions, votre exemple de
x [y ++] = 'M'; x [y ++] = 'y'; ...
Cela ferait exactement cela, la longue séquence d'instructions répétées avec une petite variation peut se démarquer et ce serait mauvais, l'octet en question peut être codé dans l'instruction tel quel et ce serait mauvais, donc peut-être la méthode xor, et peut-être quelques autres astuces pour faire ressortir cette longue section de code, certains appels de fonction factice peut-être. Cela dépend aussi de votre processeur, ARM par exemple, il est très facile de regarder les données binaires et de choisir les instructions à partir des données et de là (si vous cherchez une clé par défaut) pour éventuellement choisir ce qui pourrait être la clé car ce sont des données mais pas ascii et attaquer cela. De même un bloc d'instructions similaires avec le champ immédiat variant, même si vous avez le compilateur xor les données avec une constante.
Voici un script Perl pour générer du code c obscurci pour masquer un mot de passe en clair du programme "strings".
obfuscate_password("myPassword123");
sub obfuscate_password($) {
my $string = shift;
my @c = split(//, $string);
Push(@c, "skip"); # Skip Null Terminator
# using memset to clear this byte
# Add Decoy Characters
for($i=0; $i < 100; $i++) {
$ch = Rand(255);
next if ($ch == 0);
Push(@c, chr($ch));
}
my $count1 = @c;
print " int x1, x2, x3, x4;\n";
print " char password[$count1];\n";
print " memset(password, 0, $count1);\n";
my $count2 = 0;
my %dict = ();
while(1) {
my $x = int(Rand($count1));
$y = obfuscate_expr($count1, $x);
next if (defined($dict{$x}));
$dict{$x} = 1;
last if ($count2+1 == $count1);
if ($c[$x] ne "skip") {
#print " $y\n";
print " $y password[x4] = (char)" . ord($c[$x]) . ";\n";
}
$count2++;
}
}
sub obfuscate_expr($$) {
my $count = shift;
my $target = shift;
#return $target;
while(1) {
my $a = int(Rand($count*2));
my $b = int(Rand($count*2));
my $c = int(Rand($count*2));
next if (($a == 0) || ($b == 0) || ($c == 0));
my $y = $a - $b;
#print "$target: $y : $a - $b\n";
if ($y == $target) {
#return "$a - $b + $c";
return "x1=$a; x2=$b; x3=$c; x4=x1-x2+x3; x5= +=x4;";
}
}
}
Chiffrez la clé de chiffrement avec un autre code. Montrez une image de l'autre code à l'utilisateur. L'utilisateur doit maintenant entrer la clé qu'il voit (comme un captcha, mais toujours le même code). Cela rend également impossible pour d'autres programmes de prédire le code. En option, vous pouvez enregistrer un hachage (salé) du code pour vérifier l'entrée de l'utilisateur.