commit
4dd18c77fd
@ -374,6 +374,12 @@ int nonce_distance(uint32_t from, uint32_t to)
|
|||||||
}
|
}
|
||||||
return (65535 + dist[to >> 16] - dist[from >> 16]) % 65535;
|
return (65535 + dist[to >> 16] - dist[from >> 16]) % 65535;
|
||||||
}
|
}
|
||||||
|
bool validate_prng_nonce(uint32_t nonce)
|
||||||
|
{
|
||||||
|
// init prng table:
|
||||||
|
nonce_distance(nonce, nonce);
|
||||||
|
return ((65535 - dist[nonce >> 16] + dist[nonce & 0xffff]) % 65535) == 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static uint32_t fastfwd[2][8] = {
|
static uint32_t fastfwd[2][8] = {
|
||||||
|
|||||||
@ -20,6 +20,7 @@
|
|||||||
#ifndef CRAPTO1_INCLUDED
|
#ifndef CRAPTO1_INCLUDED
|
||||||
#define CRAPTO1_INCLUDED
|
#define CRAPTO1_INCLUDED
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
@ -39,6 +40,7 @@ extern "C" {
|
|||||||
void lfsr_rollback(struct Crypto1State *s, uint32_t in, int fb);
|
void lfsr_rollback(struct Crypto1State *s, uint32_t in, int fb);
|
||||||
uint32_t lfsr_rollback_word(struct Crypto1State *s, uint32_t in, int fb);
|
uint32_t lfsr_rollback_word(struct Crypto1State *s, uint32_t in, int fb);
|
||||||
int nonce_distance(uint32_t from, uint32_t to);
|
int nonce_distance(uint32_t from, uint32_t to);
|
||||||
|
bool validate_prng_nonce(uint32_t nonce);
|
||||||
#define FOREACH_VALID_NONCE(N, FILTER, FSIZE)\
|
#define FOREACH_VALID_NONCE(N, FILTER, FSIZE)\
|
||||||
uint32_t __n = 0,__M = 0, N = 0;\
|
uint32_t __n = 0,__M = 0, N = 0;\
|
||||||
int __i;\
|
int __i;\
|
||||||
|
|||||||
147
src/mfoc.c
147
src/mfoc.c
@ -56,15 +56,24 @@
|
|||||||
#include "slre.h"
|
#include "slre.h"
|
||||||
#include "slre.c"
|
#include "slre.c"
|
||||||
|
|
||||||
|
#define MAX_FRAME_LEN 264
|
||||||
|
|
||||||
|
static const nfc_modulation nm = {
|
||||||
|
.nmt = NMT_ISO14443A,
|
||||||
|
.nbr = NBR_106,
|
||||||
|
};
|
||||||
|
|
||||||
nfc_context *context;
|
nfc_context *context;
|
||||||
|
|
||||||
|
uint64_t knownKey = 0;
|
||||||
|
char knownKeyLetter = 'A';
|
||||||
|
uint32_t knownSector = 0;
|
||||||
|
uint32_t unknownSector = 0;
|
||||||
|
char unknownKeyLetter = 'A';
|
||||||
|
uint32_t unexpected_random = 0;
|
||||||
|
|
||||||
int main(int argc, char *const argv[])
|
int main(int argc, char *const argv[])
|
||||||
{
|
{
|
||||||
const nfc_modulation nm = {
|
|
||||||
.nmt = NMT_ISO14443A,
|
|
||||||
.nbr = NBR_106,
|
|
||||||
};
|
|
||||||
|
|
||||||
int ch, i, k, n, j, m;
|
int ch, i, k, n, j, m;
|
||||||
int key, block;
|
int key, block;
|
||||||
int succeed = 1;
|
int succeed = 1;
|
||||||
@ -117,12 +126,13 @@ int main(int argc, char *const argv[])
|
|||||||
|
|
||||||
mifare_cmd mc;
|
mifare_cmd mc;
|
||||||
FILE *pfDump = NULL;
|
FILE *pfDump = NULL;
|
||||||
|
FILE *pfKey = NULL;
|
||||||
|
|
||||||
//File pointers for the keyfile
|
//File pointers for the keyfile
|
||||||
FILE * fp;
|
FILE * fp;
|
||||||
char * line = NULL;
|
char line[20];
|
||||||
size_t len = 0;
|
size_t len = 0;
|
||||||
ssize_t read;
|
char * read;
|
||||||
|
|
||||||
//Regexp declarations
|
//Regexp declarations
|
||||||
static const char *regex = "([0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f])";
|
static const char *regex = "([0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f][0-9A-Fa-f])";
|
||||||
@ -155,9 +165,8 @@ int main(int argc, char *const argv[])
|
|||||||
fprintf(stderr, "Cannot open keyfile: %s, exiting\n", optarg);
|
fprintf(stderr, "Cannot open keyfile: %s, exiting\n", optarg);
|
||||||
exit(EXIT_FAILURE);
|
exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
while ((read = getline(&line, &len, fp)) != -1) {
|
while ((read = fgets(line, sizeof(line), fp)) != NULL) {
|
||||||
int i, j = 0, str_len = strlen(line);
|
int i, j = 0, str_len = strlen(line);
|
||||||
|
|
||||||
while (j < str_len &&
|
while (j < str_len &&
|
||||||
(i = slre_match(regex, line + j, str_len - j, caps, 500, 1)) > 0) {
|
(i = slre_match(regex, line + j, str_len - j, caps, 500, 1)) > 0) {
|
||||||
//We've found a key, let's add it to the structure.
|
//We've found a key, let's add it to the structure.
|
||||||
@ -175,8 +184,6 @@ int main(int argc, char *const argv[])
|
|||||||
j += i;
|
j += i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (line)
|
|
||||||
free(line);
|
|
||||||
break;
|
break;
|
||||||
case 'k':
|
case 'k':
|
||||||
// Add this key to the default keys
|
// Add this key to the default keys
|
||||||
@ -200,6 +207,14 @@ int main(int argc, char *const argv[])
|
|||||||
}
|
}
|
||||||
// fprintf(stdout, "Output file: %s\n", optarg);
|
// fprintf(stdout, "Output file: %s\n", optarg);
|
||||||
break;
|
break;
|
||||||
|
case 'D':
|
||||||
|
// Partial File output
|
||||||
|
if (!(pfKey = fopen(optarg, "w"))) {
|
||||||
|
fprintf(stderr, "Cannot open: %s, exiting\n", optarg);
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
// fprintf(stdout, "Output file: %s\n", optarg);
|
||||||
|
break;
|
||||||
case 'h':
|
case 'h':
|
||||||
usage(stdout, 0);
|
usage(stdout, 0);
|
||||||
break;
|
break;
|
||||||
@ -256,7 +271,7 @@ int main(int argc, char *const argv[])
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Test if a compatible MIFARE tag is used
|
// Test if a compatible MIFARE tag is used
|
||||||
if ((t.nt.nti.nai.btSak & 0x08) == 0) {
|
if (((t.nt.nti.nai.btSak & 0x08) == 0) && (t.nt.nti.nai.btSak != 0x01)) {
|
||||||
ERR("only Mifare Classic is supported");
|
ERR("only Mifare Classic is supported");
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
@ -267,11 +282,18 @@ int main(int argc, char *const argv[])
|
|||||||
// see http://www.nxp.com/documents/application_note/AN10833.pdf Section 3.2
|
// see http://www.nxp.com/documents/application_note/AN10833.pdf Section 3.2
|
||||||
switch (t.nt.nti.nai.btSak)
|
switch (t.nt.nti.nai.btSak)
|
||||||
{
|
{
|
||||||
|
case 0x01:
|
||||||
case 0x08:
|
case 0x08:
|
||||||
case 0x88:
|
case 0x88:
|
||||||
printf("Found Mifare Classic 1k tag\n");
|
if (get_rats_is_2k(t, r)) {
|
||||||
t.num_sectors = NR_TRAILERS_1k;
|
printf("Found Mifare Plus 2k tag\n");
|
||||||
t.num_blocks = NR_BLOCKS_1k;
|
t.num_sectors = NR_TRAILERS_2k;
|
||||||
|
t.num_blocks = NR_BLOCKS_2k;
|
||||||
|
} else {
|
||||||
|
printf("Found Mifare Classic 1k tag\n");
|
||||||
|
t.num_sectors = NR_TRAILERS_1k;
|
||||||
|
t.num_blocks = NR_BLOCKS_1k;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case 0x09:
|
case 0x09:
|
||||||
printf("Found Mifare Classic Mini tag\n");
|
printf("Found Mifare Classic Mini tag\n");
|
||||||
@ -421,14 +443,28 @@ int main(int argc, char *const argv[])
|
|||||||
|
|
||||||
fprintf(stdout, "\n");
|
fprintf(stdout, "\n");
|
||||||
for (i = 0; i < (t.num_sectors); ++i) {
|
for (i = 0; i < (t.num_sectors); ++i) {
|
||||||
if(t.sectors[i].foundKeyA)
|
if(t.sectors[i].foundKeyA){
|
||||||
fprintf(stdout, "Sector %02d - Found Key A: %012llx ", i, bytes_to_num(t.sectors[i].KeyA, sizeof(t.sectors[i].KeyA)));
|
fprintf(stdout, "Sector %02d - Found Key A: %012llx ", i, bytes_to_num(t.sectors[i].KeyA, sizeof(t.sectors[i].KeyA)));
|
||||||
else
|
memcpy(&knownKey, t.sectors[i].KeyA, 6);
|
||||||
|
knownKeyLetter = 'A';
|
||||||
|
knownSector = i;
|
||||||
|
}
|
||||||
|
else{
|
||||||
fprintf(stdout, "Sector %02d - Unknown Key A ", i);
|
fprintf(stdout, "Sector %02d - Unknown Key A ", i);
|
||||||
if(t.sectors[i].foundKeyB)
|
unknownSector = i;
|
||||||
|
unknownKeyLetter = 'A';
|
||||||
|
}
|
||||||
|
if(t.sectors[i].foundKeyB){
|
||||||
fprintf(stdout, "Found Key B: %012llx\n", bytes_to_num(t.sectors[i].KeyB, sizeof(t.sectors[i].KeyB)));
|
fprintf(stdout, "Found Key B: %012llx\n", bytes_to_num(t.sectors[i].KeyB, sizeof(t.sectors[i].KeyB)));
|
||||||
else
|
knownKeyLetter = 'B';
|
||||||
|
memcpy(&knownKey, t.sectors[i].KeyB, 6);
|
||||||
|
knownSector = i;
|
||||||
|
}
|
||||||
|
else{
|
||||||
fprintf(stdout, "Unknown Key B\n");
|
fprintf(stdout, "Unknown Key B\n");
|
||||||
|
unknownSector = i;
|
||||||
|
unknownKeyLetter = 'B';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
fflush(stdout);
|
fflush(stdout);
|
||||||
|
|
||||||
@ -504,7 +540,18 @@ int main(int argc, char *const argv[])
|
|||||||
// Max probes for auth for each sector
|
// Max probes for auth for each sector
|
||||||
for (k = 0; k < probes; ++k) {
|
for (k = 0; k < probes; ++k) {
|
||||||
// Try to authenticate to exploit sector and determine distances (filling denonce.distances)
|
// Try to authenticate to exploit sector and determine distances (filling denonce.distances)
|
||||||
mf_enhanced_auth(e_sector, 0, t, r, &d, pk, 'd', dumpKeysA); // AUTH + Get Distances mode
|
int authresult = mf_enhanced_auth(e_sector, 0, t, r, &d, pk, 'd', dumpKeysA); // AUTH + Get Distances mode
|
||||||
|
if(authresult == -99999){
|
||||||
|
//for now we return the last sector that is unknown
|
||||||
|
nfc_close(r.pdi);
|
||||||
|
nfc_exit(context);
|
||||||
|
if(pfKey) {
|
||||||
|
fprintf(pfKey, "%012llx;%d;%c;%d;%c", knownKey, knownSector, knownKeyLetter, unknownSector, unknownKeyLetter);
|
||||||
|
fclose(pfKey);
|
||||||
|
}
|
||||||
|
return 9;
|
||||||
|
}
|
||||||
|
|
||||||
printf("Sector: %d, type %c, probe %d, distance %d ", j, (dumpKeysA ? 'A' : 'B'), k, d.median);
|
printf("Sector: %d, type %c, probe %d, distance %d ", j, (dumpKeysA ? 'A' : 'B'), k, d.median);
|
||||||
// Configure device to the previous state
|
// Configure device to the previous state
|
||||||
mf_configure(r.pdi);
|
mf_configure(r.pdi);
|
||||||
@ -680,7 +727,7 @@ int main(int argc, char *const argv[])
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Finally save all keys + data to file
|
// Finally save all keys + data to file
|
||||||
uint16_t dump_size = (t.num_blocks + 1) * t.num_sectors;
|
uint16_t dump_size = (t.num_blocks + 1) * 16;
|
||||||
if (fwrite(&mtDump, 1, dump_size, pfDump) != dump_size) {
|
if (fwrite(&mtDump, 1, dump_size, pfDump) != dump_size) {
|
||||||
fprintf(stdout, "Error, cannot write dump\n");
|
fprintf(stdout, "Error, cannot write dump\n");
|
||||||
fclose(pfDump);
|
fclose(pfDump);
|
||||||
@ -720,6 +767,7 @@ void usage(FILE *stream, int errno)
|
|||||||
fprintf(stream, " T nonce tolerance half-range, instead of default of 20\n (i.e., 40 for the total range, in both directions)\n");
|
fprintf(stream, " T nonce tolerance half-range, instead of default of 20\n (i.e., 40 for the total range, in both directions)\n");
|
||||||
// fprintf(stream, " s specify the list of sectors to crack, for example -s 0,1,3,5\n");
|
// fprintf(stream, " s specify the list of sectors to crack, for example -s 0,1,3,5\n");
|
||||||
fprintf(stream, " O file in which the card contents will be written (REQUIRED)\n");
|
fprintf(stream, " O file in which the card contents will be written (REQUIRED)\n");
|
||||||
|
fprintf(stream, " D file in which partial card info will be written in case PRNG is not vulnerable\n");
|
||||||
fprintf(stream, "\n");
|
fprintf(stream, "\n");
|
||||||
fprintf(stream, "Example: mfoc -O mycard.mfd\n");
|
fprintf(stream, "Example: mfoc -O mycard.mfd\n");
|
||||||
fprintf(stream, "Example: mfoc -k ffffeeeedddd -O mycard.mfd\n");
|
fprintf(stream, "Example: mfoc -k ffffeeeedddd -O mycard.mfd\n");
|
||||||
@ -780,11 +828,6 @@ void mf_configure(nfc_device *pdi)
|
|||||||
|
|
||||||
void mf_select_tag(nfc_device *pdi, nfc_target *pnt)
|
void mf_select_tag(nfc_device *pdi, nfc_target *pnt)
|
||||||
{
|
{
|
||||||
// Poll for a ISO14443A (MIFARE) tag
|
|
||||||
const nfc_modulation nm = {
|
|
||||||
.nmt = NMT_ISO14443A,
|
|
||||||
.nbr = NBR_106,
|
|
||||||
};
|
|
||||||
if (nfc_initiator_select_passive_target(pdi, nm, NULL, 0, pnt) < 0) {
|
if (nfc_initiator_select_passive_target(pdi, nm, NULL, 0, pnt) < 0) {
|
||||||
ERR("Unable to connect to the MIFARE Classic tag");
|
ERR("Unable to connect to the MIFARE Classic tag");
|
||||||
nfc_close(pdi);
|
nfc_close(pdi);
|
||||||
@ -827,10 +870,6 @@ int find_exploit_sector(mftag t)
|
|||||||
|
|
||||||
void mf_anticollision(mftag t, mfreader r)
|
void mf_anticollision(mftag t, mfreader r)
|
||||||
{
|
{
|
||||||
const nfc_modulation nm = {
|
|
||||||
.nmt = NMT_ISO14443A,
|
|
||||||
.nbr = NBR_106,
|
|
||||||
};
|
|
||||||
if (nfc_initiator_select_passive_target(r.pdi, nm, NULL, 0, &t.nt) < 0) {
|
if (nfc_initiator_select_passive_target(r.pdi, nm, NULL, 0, &t.nt) < 0) {
|
||||||
nfc_perror(r.pdi, "nfc_initiator_select_passive_target");
|
nfc_perror(r.pdi, "nfc_initiator_select_passive_target");
|
||||||
ERR("Tag has been removed");
|
ERR("Tag has been removed");
|
||||||
@ -838,6 +877,48 @@ void mf_anticollision(mftag t, mfreader r)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool
|
||||||
|
get_rats_is_2k(mftag t, mfreader r)
|
||||||
|
{
|
||||||
|
int res;
|
||||||
|
uint8_t abtRx[MAX_FRAME_LEN];
|
||||||
|
int szRxBits;
|
||||||
|
uint8_t abtRats[2] = { 0xe0, 0x50};
|
||||||
|
// Use raw send/receive methods
|
||||||
|
if (nfc_device_set_property_bool(r.pdi, NP_EASY_FRAMING, false) < 0) {
|
||||||
|
nfc_perror(r.pdi, "nfc_configure");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
res = nfc_initiator_transceive_bytes(r.pdi, abtRats, sizeof(abtRats), abtRx, sizeof(abtRx), 0);
|
||||||
|
if (res > 0) {
|
||||||
|
// ISO14443-4 card, turn RF field off/on to access ISO14443-3 again
|
||||||
|
if (nfc_device_set_property_bool(r.pdi, NP_ACTIVATE_FIELD, false) < 0) {
|
||||||
|
nfc_perror(r.pdi, "nfc_configure");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (nfc_device_set_property_bool(r.pdi, NP_ACTIVATE_FIELD, true) < 0) {
|
||||||
|
nfc_perror(r.pdi, "nfc_configure");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Reselect tag
|
||||||
|
if (nfc_initiator_select_passive_target(r.pdi, nm, NULL, 0, &t.nt) <= 0) {
|
||||||
|
printf("Error: tag disappeared\n");
|
||||||
|
nfc_close(r.pdi);
|
||||||
|
nfc_exit(context);
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
if (res >= 10) {
|
||||||
|
printf("ATS %02X%02X%02X%02X%02X|%02X%02X%02X%02X\n", res, abtRx[0], abtRx[1], abtRx[2], abtRx[3], abtRx[4], abtRx[5], abtRx[6], abtRx[7], abtRx[8]);
|
||||||
|
return ((abtRx[5] == 0xc1) && (abtRx[6] == 0x05)
|
||||||
|
&& (abtRx[7] == 0x2f) && (abtRx[8] == 0x2f)
|
||||||
|
&& ((t.nt.nti.nai.abtAtqa[1] & 0x02) == 0x00));
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int mf_enhanced_auth(int e_sector, int a_sector, mftag t, mfreader r, denonce *d, pKeys *pk, char mode, bool dumpKeysA)
|
int mf_enhanced_auth(int e_sector, int a_sector, mftag t, mfreader r, denonce *d, pKeys *pk, char mode, bool dumpKeysA)
|
||||||
{
|
{
|
||||||
struct Crypto1State *pcs;
|
struct Crypto1State *pcs;
|
||||||
@ -977,9 +1058,13 @@ int mf_enhanced_auth(int e_sector, int a_sector, mftag t, mfreader r, denonce *d
|
|||||||
}
|
}
|
||||||
NtLast = bytes_to_num(Rx, 4) ^ crypto1_word(pcs, bytes_to_num(Rx, 4) ^ t.authuid, 1);
|
NtLast = bytes_to_num(Rx, 4) ^ crypto1_word(pcs, bytes_to_num(Rx, 4) ^ t.authuid, 1);
|
||||||
|
|
||||||
|
// Make sure the card is using the known PRNG
|
||||||
|
if (! validate_prng_nonce(NtLast)) {
|
||||||
|
printf("Card is not vulnerable to nested attack\n");
|
||||||
|
return -99999;
|
||||||
|
}
|
||||||
// Save the determined nonces distance
|
// Save the determined nonces distance
|
||||||
d->distances[m] = nonce_distance(Nt, NtLast);
|
d->distances[m] = nonce_distance(Nt, NtLast);
|
||||||
// fprintf(stdout, "distance: %05d\n", d->distances[m]);
|
|
||||||
|
|
||||||
// Again, prepare and send {At}
|
// Again, prepare and send {At}
|
||||||
for (i = 0; i < 4; i++) {
|
for (i = 0; i < 4; i++) {
|
||||||
|
|||||||
@ -8,6 +8,8 @@
|
|||||||
#define NR_TRAILERS_MINI (5)
|
#define NR_TRAILERS_MINI (5)
|
||||||
// Mifare Classic 4k 32x64b + 8*256b = 40
|
// Mifare Classic 4k 32x64b + 8*256b = 40
|
||||||
#define NR_TRAILERS_4k (40)
|
#define NR_TRAILERS_4k (40)
|
||||||
|
// Mifare Classic 2k 32x64b
|
||||||
|
#define NR_TRAILERS_2k (32)
|
||||||
|
|
||||||
// Number of blocks
|
// Number of blocks
|
||||||
// Mifare Classic 1k
|
// Mifare Classic 1k
|
||||||
@ -16,6 +18,8 @@
|
|||||||
#define NR_BLOCKS_MINI 0x13
|
#define NR_BLOCKS_MINI 0x13
|
||||||
// Mifare Classic 4k
|
// Mifare Classic 4k
|
||||||
#define NR_BLOCKS_4k 0xff
|
#define NR_BLOCKS_4k 0xff
|
||||||
|
// Mifare Classic 2k
|
||||||
|
#define NR_BLOCKS_2k 0x7f
|
||||||
|
|
||||||
#define MAX_FRAME_LEN 264
|
#define MAX_FRAME_LEN 264
|
||||||
|
|
||||||
@ -85,6 +89,7 @@ void mf_select_tag(nfc_device *pdi, nfc_target *pnt);
|
|||||||
int trailer_block(uint32_t block);
|
int trailer_block(uint32_t block);
|
||||||
int find_exploit_sector(mftag t);
|
int find_exploit_sector(mftag t);
|
||||||
void mf_anticollision(mftag t, mfreader r);
|
void mf_anticollision(mftag t, mfreader r);
|
||||||
|
bool get_rats_is_2k(mftag t, mfreader r);
|
||||||
int mf_enhanced_auth(int e_sector, int a_sector, mftag t, mfreader r, denonce *d, pKeys *pk, char mode, bool dumpKeysA);
|
int mf_enhanced_auth(int e_sector, int a_sector, mftag t, mfreader r, denonce *d, pKeys *pk, char mode, bool dumpKeysA);
|
||||||
uint32_t median(denonce d);
|
uint32_t median(denonce d);
|
||||||
int compar_int(const void *a, const void *b);
|
int compar_int(const void *a, const void *b);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user