33 Commits

Author SHA1 Message Date
Romuald Conty
776fb11aaf Revert "Fixed readme compiling options" 2018-01-31 18:30:12 +01:00
Romuald Conty
fa3573cfb6 Merge pull request #49 from ceres-c/patch-1
Fixed readme compiling options
2018-01-31 18:29:39 +01:00
Federico Cerutti
c24b6ac572 Updated (again) readme using a single command 2018-01-31 14:39:14 +01:00
Federico Cerutti
9fed51206f Added missing "autoconf" to readme 2018-01-20 16:27:49 +01:00
Romuald Conty
809f3bc62c Merge pull request #45 from agebhar1/patch/README.md
add 'Build from source' section to `README.md`
2018-01-09 23:07:41 +01:00
Andreas Gebhardt
b13beeac09 add 'Build from source' section to README.md 2017-10-12 19:58:15 +02:00
Philippe Teuwen
9d9f01fba4 Simplify PRNG validation 2017-02-17 16:14:01 +01:00
Philippe Teuwen
34d42e5e47 Port miLazyCracker patch: test PRNG
cf 39658a2ac4/mfoc_test_prng.diff
2017-02-17 15:44:30 +01:00
Philippe Teuwen
2316ad0815 Replace non-ANSI-C getline by fgets 2017-02-17 15:44:30 +01:00
Philippe Teuwen
0970559b97 Port miLazyCracker patch: support 2k
cf 39658a2ac4/mfoc_support_2k.diff
2017-02-17 13:52:48 +01:00
Philippe Teuwen
e36025bb25 Port miLazyCracker patch: support tnp
cf 39658a2ac4/mfoc_support_tnp.diff
2017-02-17 13:52:48 +01:00
Philippe Teuwen
f172064f98 Port miLazyCracker patch: fix 4k and Mini
cf 39658a2ac4/mfoc_fix_4k_and_mini.diff
2017-02-17 13:52:48 +01:00
Romuald Conty
48156f9bf0 Merge pull request #23 from AdamLaurie/master
Show known keys before trying to crack
2015-06-09 14:41:27 +02:00
Adam Laurie
e2cf90202a show known keys before trying to crack 2015-06-08 16:34:00 +01:00
Romuald Conty
eac78225eb Merge pull request #20 from quantum-x/master
Adds ability to load custom keys from a file
2015-04-15 14:33:33 +02:00
Simon Yorkston
51bae1e1c8 Final tweaks to regexp 2015-04-15 14:24:55 +02:00
Simon Yorkston
0976e6f285 Added -f to CL arguments 2015-04-15 13:28:50 +02:00
Simon Yorkston
eff3dc0d5f Updated SLRE syntax to comply with new version 2015-04-15 13:25:43 +02:00
Simon Yorkston
7ff1465409 Added SLRE files 2015-04-15 13:22:08 +02:00
Simon Yorkston
c346e2af72 Tweaked in the SLRE libraries 2015-04-15 13:18:09 +02:00
Simon Yorkston
86caca5f6b Adding ability to load keys from file
Switch -f allows for a file to be provided that contains additional keys
2015-04-15 12:45:15 +02:00
Romuald Conty
00eae36f89 Merge pull request #19 from kirelagin/fixes
A bunch of pretty minor fixes
2015-04-13 08:57:11 +02:00
Kirill Elagin
3b5be84676 Proper amount of data to dump 2015-04-11 23:56:21 +03:00
Kirill Elagin
f3ebde09ef Fix user-provided keys handling
This makes mfoc try user-provided keys even if the built-in
ones are removed from the code (for efficiency).
2015-04-11 23:30:58 +03:00
Kirill Elagin
b872a328e3 Fix typo
This was horrible as this made mfoc non-functional for cards
with unknown SAK’s.
2015-04-11 23:30:20 +03:00
Kirill Elagin
30ce00aa8e Alternative Mifare Classic 1k SAK 2015-04-11 23:29:59 +03:00
Romuald Conty
f3a793dc0c Merge pull request #18 from AdamLaurie/master
Don't bother cracking KeyB when you can just read it out of data block
2015-03-23 22:14:19 +01:00
Adam Laurie
5d8bf95968 don't bother cracking KeyB when you can just read it out of data block 2015-03-23 15:20:46 +00:00
Romuald Conty
1eac72641c Merge pull request #16 from socram8888/mini
Implemented Mifare Mini
2015-03-22 09:54:19 +01:00
Romuald Conty
f3558144d8 Merge pull request #15 from socram8888/master
Fix compilation warnings under Cygwin
2015-03-22 09:51:29 +01:00
Marcos Vives Del Sol
f13efb0a6d Implemented Mifare Mini using FireFart's patch 2015-03-17 16:06:16 +01:00
Marcos Vives Del Sol
a1be79d0ff Fix compilation warnings under Cygwin 2015-03-17 15:04:28 +01:00
Romuald Conty
b31ac50224 Create README.md 2015-03-14 20:51:58 +01:00
10 changed files with 828 additions and 50 deletions

2
.gitignore vendored
View File

@@ -8,6 +8,7 @@ config.h.in
config.log
config.status
configure
compile
depcomp
install-sh
missing
@@ -17,5 +18,6 @@ src/.deps/
src/Makefile
src/Makefile.in
src/mfoc
src/mfoc.exe
stamp-h1

23
README.md Normal file
View File

@@ -0,0 +1,23 @@
MFOC is an open source implementation of "offline nested" attack by Nethemba.
This program allow to recover authentication keys from MIFARE Classic card.
Please note MFOC is able to recover keys from target only if it have a known key: default one (hardcoded in MFOC) or custom one (user provided using command line).
# Build from source
```
libtoolize --force
aclocal
autoheader
automake --force-missing --add-missing
./configure
make && sudo make install
```
# Usage #
Put one MIFARE Classic tag that you want keys recovering;
Lauching mfoc, you will need to pass options, see
```
mfoc -h
```

View File

@@ -2,14 +2,14 @@ AC_INIT([mfoc],[0.10.7],[mifare@nethemba.com])
AC_CONFIG_MACRO_DIR([m4])
AC_PROG_CC
AC_CONFIG_HEADERS([config.h])
AC_CONFIG_SRCDIR([src/mfoc.c])
AM_INIT_AUTOMAKE(dist-bzip2 no-dist-gzip)
AC_PROG_CC
m4_ifdef([AM_SILENT_RULES],[AM_SILENT_RULES([yes])])
# Checks for pkg-config modules.

0
m4/.empty Normal file
View File

View File

@@ -374,6 +374,12 @@ int nonce_distance(uint32_t from, uint32_t to)
}
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] = {

View File

@@ -20,6 +20,7 @@
#ifndef CRAPTO1_INCLUDED
#define CRAPTO1_INCLUDED
#include <stdint.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
@@ -39,6 +40,7 @@ extern "C" {
void lfsr_rollback(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);
bool validate_prng_nonce(uint32_t nonce);
#define FOREACH_VALID_NONCE(N, FILTER, FSIZE)\
uint32_t __n = 0,__M = 0, N = 0;\
int __i;\

View File

@@ -52,15 +52,28 @@
#include "nfc-utils.h"
#include "mfoc.h"
//SLRE
#include "slre.h"
#include "slre.c"
#define MAX_FRAME_LEN 264
static const nfc_modulation nm = {
.nmt = NMT_ISO14443A,
.nbr = NBR_106,
};
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[])
{
const nfc_modulation nm = {
.nmt = NMT_ISO14443A,
.nbr = NBR_106,
};
int ch, i, k, n, j, m;
int key, block;
int succeed = 1;
@@ -97,25 +110,36 @@ int main(int argc, char *const argv[])
};
mftag t;
mfreader r;
denonce d = {NULL, 0, DEFAULT_DIST_NR, DEFAULT_TOLERANCE, {0x00, 0x00, 0x00}};
mftag t;
mfreader r;
denonce d = {NULL, 0, DEFAULT_DIST_NR, DEFAULT_TOLERANCE, {0x00, 0x00, 0x00}};
// Pointers to possible keys
pKeys *pk;
countKeys *ck;
pKeys *pk;
countKeys *ck;
// Pointer to already broken keys, except defaults
bKeys *bk;
bKeys *bk;
static mifare_param mp;
static mifare_param mp, mtmp;
static mifare_classic_tag mtDump;
mifare_cmd mc;
FILE *pfDump = NULL;
FILE *pfKey = NULL;
//File pointers for the keyfile
FILE * fp;
char line[20];
size_t len = 0;
char * read;
//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])";
struct slre_cap caps[2];
// Parse command line arguments
while ((ch = getopt(argc, argv, "hD:s:BP:T:S:O:k:t:")) != -1) {
while ((ch = getopt(argc, argv, "hD:s:BP:T:S:O:k:t:f:")) != -1) {
switch (ch) {
case 'P':
// Number of probes
@@ -136,6 +160,31 @@ int main(int argc, char *const argv[])
// fprintf(stdout, "Tolerance number: %d\n", probes);
}
break;
case 'f':
if (!(fp = fopen(optarg, "r"))) {
fprintf(stderr, "Cannot open keyfile: %s, exiting\n", optarg);
exit(EXIT_FAILURE);
}
while ((read = fgets(line, sizeof(line), fp)) != NULL) {
int i, j = 0, str_len = strlen(line);
while (j < str_len &&
(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.
p = realloc(defKeys, defKeys_len + 6);
if (!p) {
ERR("Cannot allocate memory for defKeys");
exit(EXIT_FAILURE);
}
defKeys = p;
memset(defKeys + defKeys_len, 0, 6);
num_to_bytes(strtoll(caps[0].ptr, NULL, 16), 6, defKeys + defKeys_len);
fprintf(stdout, "The custom key 0x%.*s has been added to the default keys\n", caps[0].len, caps[0].ptr);
defKeys_len = defKeys_len + 6;
j += i;
}
}
break;
case 'k':
// Add this key to the default keys
p = realloc(defKeys, defKeys_len + 6);
@@ -158,6 +207,14 @@ int main(int argc, char *const argv[])
}
// fprintf(stdout, "Output file: %s\n", optarg);
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':
usage(stdout, 0);
break;
@@ -200,8 +257,8 @@ int main(int argc, char *const argv[])
}
/*
// wait for tag to appear
for (i=0;!nfc_initiator_select_passive_target(r.pdi, nm, NULL, 0, &t.nt) && i < 10; i++) zsleep (100);
// wait for tag to appear
for (i=0;!nfc_initiator_select_passive_target(r.pdi, nm, NULL, 0, &t.nt) && i < 10; i++) zsleep (100);
*/
int tag_count;
@@ -214,17 +271,44 @@ int main(int argc, char *const argv[])
}
// 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");
goto error;
}
// Save tag's block size (b4K)
t.b4K = (t.nt.nti.nai.abtAtqa[1] == 0x02);
t.authuid = (uint32_t) bytes_to_num(t.nt.nti.nai.abtUid + t.nt.nti.nai.szUidLen - 4, 4);
t.num_blocks = (t.b4K) ? 0xff : 0x3f;
t.num_sectors = t.b4K ? NR_TRAILERS_4k : NR_TRAILERS_1k;
// Get Mifare Classic type from SAK
// see http://www.nxp.com/documents/application_note/AN10833.pdf Section 3.2
switch (t.nt.nti.nai.btSak)
{
case 0x01:
case 0x08:
case 0x88:
if (get_rats_is_2k(t, r)) {
printf("Found Mifare Plus 2k tag\n");
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;
case 0x09:
printf("Found Mifare Classic Mini tag\n");
t.num_sectors = NR_TRAILERS_MINI;
t.num_blocks = NR_BLOCKS_MINI;
break;
case 0x18:
printf("Found Mifare Classic 4k tag\n");
t.num_sectors = NR_TRAILERS_4k;
t.num_blocks = NR_BLOCKS_4k;
break;
default:
ERR("Cannot determine card type from SAK");
goto error;
}
t.sectors = (void *) calloc(t.num_sectors, sizeof(sector));
if (t.sectors == NULL) {
@@ -264,7 +348,7 @@ int main(int argc, char *const argv[])
n = sizeof(defaultKeys) / sizeof(defaultKeys[0]);
size_t defKey_bytes_todo = defKeys_len;
key = 0;
while (key < n) {
while (key < n || defKey_bytes_todo) {
if (defKey_bytes_todo > 0) {
memcpy(mp.mpa.abtKey, defKeys + defKeys_len - defKey_bytes_todo, sizeof(mp.mpa.abtKey));
defKey_bytes_todo -= sizeof(mp.mpa.abtKey);
@@ -291,8 +375,39 @@ int main(int argc, char *const argv[])
// Save all information about successfull keyA authentization
memcpy(t.sectors[i].KeyA, mp.mpa.abtKey, sizeof(mp.mpa.abtKey));
t.sectors[i].foundKeyA = true;
// Although KeyA can never be directly read from the data sector, KeyB can, so
// if we need KeyB for this sector, it should be revealed by a data read with KeyA
// todo - check for duplicates in cracked key list (do we care? will not be huge overhead)
// todo - make code more modular! :)
if (!t.sectors[i].foundKeyB) {
if ((res = nfc_initiator_mifare_cmd(r.pdi, MC_READ, block, &mtmp)) >= 0) {
// print only for debugging as it messes up output!
//fprintf(stdout, " Data read with Key A revealed Key B: [%012llx] - checking Auth: ", bytes_to_num(mtmp.mpd.abtData + 10, sizeof(mtmp.mpa.abtKey)));
memcpy(mtmp.mpa.abtKey, mtmp.mpd.abtData + 10, sizeof(mtmp.mpa.abtKey));
memcpy(mtmp.mpa.abtAuthUid, t.nt.nti.nai.abtUid + t.nt.nti.nai.szUidLen - 4, sizeof(mtmp.mpa.abtAuthUid));
if ((res = nfc_initiator_mifare_cmd(r.pdi, MC_AUTH_B, block, &mtmp)) < 0) {
//fprintf(stdout, "Failed!\n");
mf_configure(r.pdi);
mf_anticollision(t, r);
} else {
//fprintf(stdout, "OK\n");
memcpy(t.sectors[i].KeyB, mtmp.mpd.abtData + 10, sizeof(t.sectors[i].KeyB));
t.sectors[i].foundKeyB = true;
bk->size++;
bk->brokenKeys = (uint64_t *) realloc((void *)bk->brokenKeys, bk->size * sizeof(uint64_t));
bk->brokenKeys[bk->size - 1] = bytes_to_num(mtmp.mpa.abtKey, sizeof(mtmp.mpa.abtKey));
}
} else {
if (res != NFC_ERFTRANS) {
nfc_perror(r.pdi, "nfc_initiator_mifare_cmd");
goto error;
}
mf_anticollision(t, r);
}
}
}
}
// if key reveal failed, try other keys
if (!t.sectors[i].foundKeyB) {
mc = MC_AUTH_B;
int res;
@@ -328,9 +443,28 @@ int main(int argc, char *const argv[])
fprintf(stdout, "\n");
for (i = 0; i < (t.num_sectors); ++i) {
fprintf(stdout, "Sector %02d - %12s ", i, ((t.sectors[i].foundKeyA) ? " FOUND_KEY [A]" : " UNKNOWN_KEY [A]"));
fprintf(stdout, "Sector %02d - %12s ", i, ((t.sectors[i].foundKeyB) ? " FOUND_KEY [B]" : " UNKNOWN_KEY [B]"));
fprintf(stdout, "\n");
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)));
memcpy(&knownKey, t.sectors[i].KeyA, 6);
knownKeyLetter = 'A';
knownSector = i;
}
else{
fprintf(stdout, "Sector %02d - Unknown Key A ", i);
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)));
knownKeyLetter = 'B';
memcpy(&knownKey, t.sectors[i].KeyB, 6);
knownSector = i;
}
else{
fprintf(stdout, "Unknown Key B\n");
unknownSector = i;
unknownKeyLetter = 'B';
}
}
fflush(stdout);
@@ -359,15 +493,41 @@ int main(int argc, char *const argv[])
mf_anticollision(t, r);
} else {
// Save all information about successfull authentization
printf("Sector: %d, type %c\n", j, (dumpKeysA ? 'A' : 'B'));
if (dumpKeysA) {
memcpy(t.sectors[j].KeyA, mp.mpa.abtKey, sizeof(mp.mpa.abtKey));
t.sectors[j].foundKeyA = true;
// if we need KeyB for this sector it should be revealed by a data read with KeyA
if (!t.sectors[j].foundKeyB) {
if ((res = nfc_initiator_mifare_cmd(r.pdi, MC_READ, t.sectors[j].trailer, &mtmp)) >= 0) {
fprintf(stdout, " Data read with Key A revealed Key B: [%012llx] - checking Auth: ", bytes_to_num(mtmp.mpd.abtData + 10, sizeof(mtmp.mpa.abtKey)));
memcpy(mtmp.mpa.abtKey, mtmp.mpd.abtData + 10, sizeof(mtmp.mpa.abtKey));
memcpy(mtmp.mpa.abtAuthUid, t.nt.nti.nai.abtUid + t.nt.nti.nai.szUidLen - 4, sizeof(mtmp.mpa.abtAuthUid));
if ((res = nfc_initiator_mifare_cmd(r.pdi, MC_AUTH_B, t.sectors[j].trailer, &mtmp)) < 0) {
fprintf(stdout, "Failed!\n");
mf_configure(r.pdi);
mf_anticollision(t, r);
} else {
fprintf(stdout, "OK\n");
memcpy(t.sectors[j].KeyB, mtmp.mpd.abtData + 10, sizeof(t.sectors[j].KeyB));
t.sectors[j].foundKeyB = true;
bk->size++;
bk->brokenKeys = (uint64_t *) realloc((void *)bk->brokenKeys, bk->size * sizeof(uint64_t));
bk->brokenKeys[bk->size - 1] = bytes_to_num(mtmp.mpa.abtKey, sizeof(mtmp.mpa.abtKey));
}
} else {
if (res != NFC_ERFTRANS) {
nfc_perror(r.pdi, "nfc_initiator_mifare_cmd");
goto error;
}
mf_anticollision(t, r);
}
}
} else {
memcpy(t.sectors[j].KeyB, mp.mpa.abtKey, sizeof(mp.mpa.abtKey));
t.sectors[j].foundKeyB = true;
}
printf("Sector: %d, type %c\n", j, (dumpKeysA ? 'A' : 'B'));
fprintf(stdout, "Found Key: %c [%012llx]\n", (dumpKeysA ? 'A' : 'B'),
fprintf(stdout, " Found Key: %c [%012llx]\n", (dumpKeysA ? 'A' : 'B'),
bytes_to_num(mp.mpa.abtKey, 6));
mf_configure(r.pdi);
mf_anticollision(t, r);
@@ -380,7 +540,18 @@ int main(int argc, char *const argv[])
// Max probes for auth for each sector
for (k = 0; k < probes; ++k) {
// 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);
// Configure device to the previous state
mf_configure(r.pdi);
@@ -419,7 +590,7 @@ int main(int argc, char *const argv[])
// Save all information about successfull authentization
bk->size++;
bk->brokenKeys = (uint64_t *) realloc((void *)bk->brokenKeys, bk->size * sizeof(uint64_t));
bk->brokenKeys[bk->size - 1] = bytes_to_num(mp.mpa.abtKey, 6);
bk->brokenKeys[bk->size - 1] = bytes_to_num(mp.mpa.abtKey, sizeof(mp.mpa.abtKey));
if (dumpKeysA) {
memcpy(t.sectors[j].KeyA, mp.mpa.abtKey, sizeof(mp.mpa.abtKey));
t.sectors[j].foundKeyA = true;
@@ -428,8 +599,34 @@ int main(int argc, char *const argv[])
memcpy(t.sectors[j].KeyB, mp.mpa.abtKey, sizeof(mp.mpa.abtKey));
t.sectors[j].foundKeyB = true;
}
fprintf(stdout, "Found Key: %c [%012llx]\n", (dumpKeysA ? 'A' : 'B'),
fprintf(stdout, " Found Key: %c [%012llx]\n", (dumpKeysA ? 'A' : 'B'),
bytes_to_num(mp.mpa.abtKey, 6));
// if we need KeyB for this sector, it should be revealed by a data read with KeyA
if (!t.sectors[j].foundKeyB) {
if ((res = nfc_initiator_mifare_cmd(r.pdi, MC_READ, t.sectors[j].trailer, &mtmp)) >= 0) {
fprintf(stdout, " Data read with Key A revealed Key B: [%012llx] - checking Auth: ", bytes_to_num(mtmp.mpd.abtData + 10, sizeof(mtmp.mpa.abtKey)));
memcpy(mtmp.mpa.abtKey, mtmp.mpd.abtData + 10, sizeof(mtmp.mpa.abtKey));
memcpy(mtmp.mpa.abtAuthUid, t.nt.nti.nai.abtUid + t.nt.nti.nai.szUidLen - 4, sizeof(mtmp.mpa.abtAuthUid));
if ((res = nfc_initiator_mifare_cmd(r.pdi, MC_AUTH_B, t.sectors[j].trailer, &mtmp)) < 0) {
fprintf(stdout, "Failed!\n");
mf_configure(r.pdi);
mf_anticollision(t, r);
} else {
fprintf(stdout, "OK\n");
memcpy(t.sectors[j].KeyB, mtmp.mpd.abtData + 10, sizeof(t.sectors[j].KeyB));
t.sectors[j].foundKeyB = true;
bk->size++;
bk->brokenKeys = (uint64_t *) realloc((void *)bk->brokenKeys, bk->size * sizeof(uint64_t));
bk->brokenKeys[bk->size - 1] = bytes_to_num(mtmp.mpa.abtKey, sizeof(mtmp.mpa.abtKey));
}
} else {
if (res != NFC_ERFTRANS) {
nfc_perror(r.pdi, "nfc_initiator_mifare_cmd");
goto error;
}
mf_anticollision(t, r);
}
}
mf_configure(r.pdi);
mf_anticollision(t, r);
break;
@@ -530,7 +727,8 @@ int main(int argc, char *const argv[])
}
// Finally save all keys + data to file
if (fwrite(&mtDump, 1, sizeof(mtDump), pfDump) != sizeof(mtDump)) {
uint16_t dump_size = (t.num_blocks + 1) * 16;
if (fwrite(&mtDump, 1, dump_size, pfDump) != dump_size) {
fprintf(stdout, "Error, cannot write dump\n");
fclose(pfDump);
goto error;
@@ -557,20 +755,23 @@ error:
void usage(FILE *stream, int errno)
{
fprintf(stream, "Usage: mfoc [-h] [-k key]... [-P probnum] [-T tolerance] [-O output]\n");
fprintf(stream, "Usage: mfoc [-h] [-k key] [-f file] ... [-P probnum] [-T tolerance] [-O output]\n");
fprintf(stream, "\n");
fprintf(stream, " h print this help and exit\n");
// fprintf(stream, " B instead of 'A' dump 'B' keys\n");
// fprintf(stream, " B instead of 'A' dump 'B' keys\n");
fprintf(stream, " k try the specified key in addition to the default keys\n");
// fprintf(stream, " D number of distance probes, default is 20\n");
// fprintf(stream, " S number of sets with keystreams, default is 5\n");
fprintf(stream, " f parses a file of keys to add in addition to the default keys \n");
// fprintf(stream, " D number of distance probes, default is 20\n");
// fprintf(stream, " S number of sets with keystreams, default is 5\n");
fprintf(stream, " P number of probes per sector, instead of default of 20\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, " D file in which partial card info will be written in case PRNG is not vulnerable\n");
fprintf(stream, "\n");
fprintf(stream, "Example: mfoc -O mycard.mfd\n");
fprintf(stream, "Example: mfoc -k ffffeeeedddd -O mycard.mfd\n");
fprintf(stream, "Example: mfoc -f keys.txt -O mycard.mfd\n");
fprintf(stream, "Example: mfoc -P 50 -T 30 -O mycard.mfd\n");
fprintf(stream, "\n");
fprintf(stream, "This is mfoc version %s.\n", PACKAGE_VERSION);
@@ -627,11 +828,6 @@ void mf_configure(nfc_device *pdi)
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) {
ERR("Unable to connect to the MIFARE Classic tag");
nfc_close(pdi);
@@ -674,10 +870,6 @@ int find_exploit_sector(mftag t)
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) {
nfc_perror(r.pdi, "nfc_initiator_select_passive_target");
ERR("Tag has been removed");
@@ -685,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)
{
struct Crypto1State *pcs;
@@ -824,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);
// 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
d->distances[m] = nonce_distance(Nt, NtLast);
// fprintf(stdout, "distance: %05d\n", d->distances[m]);
// Again, prepare and send {At}
for (i = 0; i < 4; i++) {

View File

@@ -2,10 +2,24 @@
#define TRY_KEYS 50
// Number of trailers == number of sectors
// 16x64b = 16
// Mifare Classic 1k 16x64b = 16
#define NR_TRAILERS_1k (16)
// 32x64b + 8*256b = 40
// Mifare Classic Mini
#define NR_TRAILERS_MINI (5)
// Mifare Classic 4k 32x64b + 8*256b = 40
#define NR_TRAILERS_4k (40)
// Mifare Classic 2k 32x64b
#define NR_TRAILERS_2k (32)
// Number of blocks
// Mifare Classic 1k
#define NR_BLOCKS_1k 0x3f
// Mifare Classic Mini
#define NR_BLOCKS_MINI 0x13
// Mifare Classic 4k
#define NR_BLOCKS_4k 0xff
// Mifare Classic 2k
#define NR_BLOCKS_2k 0x7f
#define MAX_FRAME_LEN 264
@@ -46,7 +60,6 @@ typedef struct {
uint8_t num_sectors;
uint8_t num_blocks;
uint32_t authuid;
bool b4K;
} mftag;
typedef struct {
@@ -76,6 +89,7 @@ void mf_select_tag(nfc_device *pdi, nfc_target *pnt);
int trailer_block(uint32_t block);
int find_exploit_sector(mftag t);
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);
uint32_t median(denonce d);
int compar_int(const void *a, const void *b);

433
src/slre.c Normal file
View File

@@ -0,0 +1,433 @@
/*
* Copyright (c) 2004-2013 Sergey Lyubka <valenok@gmail.com>
* Copyright (c) 2013 Cesanta Software Limited
* All rights reserved
*
* This library is dual-licensed: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation. For the terms of this
* license, see <http://www.gnu.org/licenses/>.
*
* You are free to use this library under the terms of the GNU General
* Public License, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* Alternatively, you can license this library under a commercial
* license, as set out in <http://cesanta.com/products.html>.
*/
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include "slre.h"
#define MAX_BRANCHES 100
#define MAX_BRACKETS 100
#define FAIL_IF(condition, error_code) if (condition) return (error_code)
#ifndef ARRAY_SIZE
#define ARRAY_SIZE(ar) (sizeof(ar) / sizeof((ar)[0]))
#endif
#ifdef SLRE_DEBUG
#define DBG(x) printf x
#else
#define DBG(x)
#endif
struct bracket_pair {
const char *ptr; /* Points to the first char after '(' in regex */
int len; /* Length of the text between '(' and ')' */
int branches; /* Index in the branches array for this pair */
int num_branches; /* Number of '|' in this bracket pair */
};
struct branch {
int bracket_index; /* index for 'struct bracket_pair brackets' */
/* array defined below */
const char *schlong; /* points to the '|' character in the regex */
};
struct regex_info {
/*
* Describes all bracket pairs in the regular expression.
* First entry is always present, and grabs the whole regex.
*/
struct bracket_pair brackets[MAX_BRACKETS];
int num_brackets;
/*
* Describes alternations ('|' operators) in the regular expression.
* Each branch falls into a specific branch pair.
*/
struct branch branches[MAX_BRANCHES];
int num_branches;
/* Array of captures provided by the user */
struct slre_cap *caps;
int num_caps;
/* E.g. SLRE_IGNORE_CASE. See enum below */
int flags;
};
static int is_metacharacter(const unsigned char *s) {
static const char *metacharacters = "^$().[]*+?|\\Ssdbfnrtv";
return strchr(metacharacters, *s) != NULL;
}
static int op_len(const char *re) {
return re[0] == '\\' && re[1] == 'x' ? 4 : re[0] == '\\' ? 2 : 1;
}
static int set_len(const char *re, int re_len) {
int len = 0;
while (len < re_len && re[len] != ']') {
len += op_len(re + len);
}
return len <= re_len ? len + 1 : -1;
}
static int get_op_len(const char *re, int re_len) {
return re[0] == '[' ? set_len(re + 1, re_len - 1) + 1 : op_len(re);
}
static int is_quantifier(const char *re) {
return re[0] == '*' || re[0] == '+' || re[0] == '?';
}
static int toi(int x) {
return isdigit(x) ? x - '0' : x - 'W';
}
static int hextoi(const unsigned char *s) {
return (toi(tolower(s[0])) << 4) | toi(tolower(s[1]));
}
static int match_op(const unsigned char *re, const unsigned char *s,
struct regex_info *info) {
int result = 0;
switch (*re) {
case '\\':
/* Metacharacters */
switch (re[1]) {
case 'S': FAIL_IF(isspace(*s), SLRE_NO_MATCH); result++; break;
case 's': FAIL_IF(!isspace(*s), SLRE_NO_MATCH); result++; break;
case 'd': FAIL_IF(!isdigit(*s), SLRE_NO_MATCH); result++; break;
case 'b': FAIL_IF(*s != '\b', SLRE_NO_MATCH); result++; break;
case 'f': FAIL_IF(*s != '\f', SLRE_NO_MATCH); result++; break;
case 'n': FAIL_IF(*s != '\n', SLRE_NO_MATCH); result++; break;
case 'r': FAIL_IF(*s != '\r', SLRE_NO_MATCH); result++; break;
case 't': FAIL_IF(*s != '\t', SLRE_NO_MATCH); result++; break;
case 'v': FAIL_IF(*s != '\v', SLRE_NO_MATCH); result++; break;
case 'x':
/* Match byte, \xHH where HH is hexadecimal byte representaion */
FAIL_IF(hextoi(re + 2) != *s, SLRE_NO_MATCH);
result++;
break;
default:
/* Valid metacharacter check is done in bar() */
FAIL_IF(re[1] != s[0], SLRE_NO_MATCH);
result++;
break;
}
break;
case '|': FAIL_IF(1, SLRE_INTERNAL_ERROR); break;
case '$': FAIL_IF(1, SLRE_NO_MATCH); break;
case '.': result++; break;
default:
if (info->flags & SLRE_IGNORE_CASE) {
FAIL_IF(tolower(*re) != tolower(*s), SLRE_NO_MATCH);
} else {
FAIL_IF(*re != *s, SLRE_NO_MATCH);
}
result++;
break;
}
return result;
}
static int match_set(const char *re, int re_len, const char *s,
struct regex_info *info) {
int len = 0, result = -1, invert = re[0] == '^';
if (invert) re++, re_len--;
while (len <= re_len && re[len] != ']' && result <= 0) {
/* Support character range */
if (re[len] != '-' && re[len + 1] == '-' && re[len + 2] != ']' &&
re[len + 2] != '\0') {
result = info->flags && SLRE_IGNORE_CASE ?
*s >= re[len] && *s <= re[len + 2] :
tolower(*s) >= tolower(re[len]) && tolower(*s) <= tolower(re[len + 2]);
len += 3;
} else {
result = match_op((unsigned char *) re + len, (unsigned char *) s, info);
len += op_len(re + len);
}
}
return (!invert && result > 0) || (invert && result <= 0) ? 1 : -1;
}
static int doh(const char *s, int s_len, struct regex_info *info, int bi);
static int bar(const char *re, int re_len, const char *s, int s_len,
struct regex_info *info, int bi) {
/* i is offset in re, j is offset in s, bi is brackets index */
int i, j, n, step;
for (i = j = 0; i < re_len && j <= s_len; i += step) {
/* Handle quantifiers. Get the length of the chunk. */
step = re[i] == '(' ? info->brackets[bi + 1].len + 2 :
get_op_len(re + i, re_len - i);
DBG(("%s [%.*s] [%.*s] re_len=%d step=%d i=%d j=%d\n", __func__,
re_len - i, re + i, s_len - j, s + j, re_len, step, i, j));
FAIL_IF(is_quantifier(&re[i]), SLRE_UNEXPECTED_QUANTIFIER);
FAIL_IF(step <= 0, SLRE_INVALID_CHARACTER_SET);
if (i + step < re_len && is_quantifier(re + i + step)) {
DBG(("QUANTIFIER: [%.*s]%c [%.*s]\n", step, re + i,
re[i + step], s_len - j, s + j));
if (re[i + step] == '?') {
int result = bar(re + i, step, s + j, s_len - j, info, bi);
j += result > 0 ? result : 0;
i++;
} else if (re[i + step] == '+' || re[i + step] == '*') {
int j2 = j, nj = j, n1, n2 = -1, ni, non_greedy = 0;
/* Points to the regexp code after the quantifier */
ni = i + step + 1;
if (ni < re_len && re[ni] == '?') {
non_greedy = 1;
ni++;
}
do {
if ((n1 = bar(re + i, step, s + j2, s_len - j2, info, bi)) > 0) {
j2 += n1;
}
if (re[i + step] == '+' && n1 < 0) break;
if (ni >= re_len) {
/* After quantifier, there is nothing */
nj = j2;
} else if ((n2 = bar(re + ni, re_len - ni, s + j2,
s_len - j2, info, bi)) >= 0) {
/* Regex after quantifier matched */
nj = j2 + n2;
}
if (nj > j && non_greedy) break;
} while (n1 > 0);
if (n1 < 0 && re[i + step] == '*' &&
(n2 = bar(re + ni, re_len - ni, s + j, s_len - j, info, bi)) > 0) {
nj = j + n2;
}
DBG(("STAR/PLUS END: %d %d %d %d %d\n", j, nj, re_len - ni, n1, n2));
FAIL_IF(re[i + step] == '+' && nj == j, SLRE_NO_MATCH);
/* If while loop body above was not executed for the * quantifier, */
/* make sure the rest of the regex matches */
FAIL_IF(nj == j && ni < re_len && n2 < 0, SLRE_NO_MATCH);
/* Returning here cause we've matched the rest of RE already */
return nj;
}
continue;
}
if (re[i] == '[') {
n = match_set(re + i + 1, re_len - (i + 2), s + j, info);
DBG(("SET %.*s [%.*s] -> %d\n", step, re + i, s_len - j, s + j, n));
FAIL_IF(n <= 0, SLRE_NO_MATCH);
j += n;
} else if (re[i] == '(') {
n = SLRE_NO_MATCH;
bi++;
FAIL_IF(bi >= info->num_brackets, SLRE_INTERNAL_ERROR);
DBG(("CAPTURING [%.*s] [%.*s] [%s]\n",
step, re + i, s_len - j, s + j, re + i + step));
if (re_len - (i + step) <= 0) {
/* Nothing follows brackets */
n = doh(s + j, s_len - j, info, bi);
} else {
int j2;
for (j2 = 0; j2 <= s_len - j; j2++) {
if ((n = doh(s + j, s_len - (j + j2), info, bi)) >= 0 &&
bar(re + i + step, re_len - (i + step),
s + j + n, s_len - (j + n), info, bi) >= 0) break;
}
}
DBG(("CAPTURED [%.*s] [%.*s]:%d\n", step, re + i, s_len - j, s + j, n));
FAIL_IF(n < 0, n);
if (info->caps != NULL) {
info->caps[bi - 1].ptr = s + j;
info->caps[bi - 1].len = n;
}
j += n;
} else if (re[i] == '^') {
FAIL_IF(j != 0, SLRE_NO_MATCH);
} else if (re[i] == '$') {
FAIL_IF(j != s_len, SLRE_NO_MATCH);
} else {
FAIL_IF(j >= s_len, SLRE_NO_MATCH);
n = match_op((unsigned char *) (re + i), (unsigned char *) (s + j), info);
FAIL_IF(n <= 0, n);
j += n;
}
}
return j;
}
/* Process branch points */
static int doh(const char *s, int s_len, struct regex_info *info, int bi) {
const struct bracket_pair *b = &info->brackets[bi];
int i = 0, len, result;
const char *p;
do {
p = i == 0 ? b->ptr : info->branches[b->branches + i - 1].schlong + 1;
len = b->num_branches == 0 ? b->len :
i == b->num_branches ? (int) (b->ptr + b->len - p) :
(int) (info->branches[b->branches + i].schlong - p);
DBG(("%s %d %d [%.*s] [%.*s]\n", __func__, bi, i, len, p, s_len, s));
result = bar(p, len, s, s_len, info, bi);
DBG(("%s <- %d\n", __func__, result));
} while (result <= 0 && i++ < b->num_branches); /* At least 1 iteration */
return result;
}
static int baz(const char *s, int s_len, struct regex_info *info) {
int i, result = -1, is_anchored = info->brackets[0].ptr[0] == '^';
for (i = 0; i <= s_len; i++) {
result = doh(s + i, s_len - i, info, 0);
if (result >= 0) {
result += i;
break;
}
if (is_anchored) break;
}
return result;
}
static void setup_branch_points(struct regex_info *info) {
int i, j;
struct branch tmp;
/* First, sort branches. Must be stable, no qsort. Use bubble algo. */
for (i = 0; i < info->num_branches; i++) {
for (j = i + 1; j < info->num_branches; j++) {
if (info->branches[i].bracket_index > info->branches[j].bracket_index) {
tmp = info->branches[i];
info->branches[i] = info->branches[j];
info->branches[j] = tmp;
}
}
}
/*
* For each bracket, set their branch points. This way, for every bracket
* (i.e. every chunk of regex) we know all branch points before matching.
*/
for (i = j = 0; i < info->num_brackets; i++) {
info->brackets[i].num_branches = 0;
info->brackets[i].branches = j;
while (j < info->num_branches && info->branches[j].bracket_index == i) {
info->brackets[i].num_branches++;
j++;
}
}
}
static int foo(const char *re, int re_len, const char *s, int s_len,
struct regex_info *info) {
int i, step, depth = 0;
/* First bracket captures everything */
info->brackets[0].ptr = re;
info->brackets[0].len = re_len;
info->num_brackets = 1;
/* Make a single pass over regex string, memorize brackets and branches */
for (i = 0; i < re_len; i += step) {
step = get_op_len(re + i, re_len - i);
if (re[i] == '|') {
FAIL_IF(info->num_branches >= (int) ARRAY_SIZE(info->branches),
SLRE_TOO_MANY_BRANCHES);
info->branches[info->num_branches].bracket_index =
info->brackets[info->num_brackets - 1].len == -1 ?
info->num_brackets - 1 : depth;
info->branches[info->num_branches].schlong = &re[i];
info->num_branches++;
} else if (re[i] == '\\') {
FAIL_IF(i >= re_len - 1, SLRE_INVALID_METACHARACTER);
if (re[i + 1] == 'x') {
/* Hex digit specification must follow */
FAIL_IF(re[i + 1] == 'x' && i >= re_len - 3,
SLRE_INVALID_METACHARACTER);
FAIL_IF(re[i + 1] == 'x' && !(isxdigit(re[i + 2]) &&
isxdigit(re[i + 3])), SLRE_INVALID_METACHARACTER);
} else {
FAIL_IF(!is_metacharacter((unsigned char *) re + i + 1),
SLRE_INVALID_METACHARACTER);
}
} else if (re[i] == '(') {
FAIL_IF(info->num_brackets >= (int) ARRAY_SIZE(info->brackets),
SLRE_TOO_MANY_BRACKETS);
depth++; /* Order is important here. Depth increments first. */
info->brackets[info->num_brackets].ptr = re + i + 1;
info->brackets[info->num_brackets].len = -1;
info->num_brackets++;
FAIL_IF(info->num_caps > 0 && info->num_brackets - 1 > info->num_caps,
SLRE_CAPS_ARRAY_TOO_SMALL);
} else if (re[i] == ')') {
int ind = info->brackets[info->num_brackets - 1].len == -1 ?
info->num_brackets - 1 : depth;
info->brackets[ind].len = (int) (&re[i] - info->brackets[ind].ptr);
DBG(("SETTING BRACKET %d [%.*s]\n",
ind, info->brackets[ind].len, info->brackets[ind].ptr));
depth--;
FAIL_IF(depth < 0, SLRE_UNBALANCED_BRACKETS);
FAIL_IF(i > 0 && re[i - 1] == '(', SLRE_NO_MATCH);
}
}
FAIL_IF(depth != 0, SLRE_UNBALANCED_BRACKETS);
setup_branch_points(info);
return baz(s, s_len, info);
}
int slre_match(const char *regexp, const char *s, int s_len,
struct slre_cap *caps, int num_caps, int flags) {
struct regex_info info;
/* Initialize info structure */
info.flags = flags;
info.num_brackets = info.num_branches = 0;
info.num_caps = num_caps;
info.caps = caps;
DBG(("========================> [%s] [%.*s]\n", regexp, s_len, s));
return foo(regexp, (int) strlen(regexp), s, s_len, &info);
}

60
src/slre.h Normal file
View File

@@ -0,0 +1,60 @@
/*
* Copyright (c) 2004-2013 Sergey Lyubka <valenok@gmail.com>
* Copyright (c) 2013 Cesanta Software Limited
* All rights reserved
*
* This library is dual-licensed: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation. For the terms of this
* license, see <http://www.gnu.org/licenses/>.
*
* You are free to use this library under the terms of the GNU General
* Public License, but WITHOUT ANY WARRANTY; without even the implied
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* Alternatively, you can license this library under a commercial
* license, as set out in <http://cesanta.com/products.html>.
*/
/*
* This is a regular expression library that implements a subset of Perl RE.
* Please refer to README.md for a detailed reference.
*/
#ifndef SLRE_HEADER_DEFINED
#define SLRE_HEADER_DEFINED
#ifdef __cplusplus
extern "C" {
#endif
struct slre_cap {
const char *ptr;
int len;
};
int slre_match(const char *regexp, const char *buf, int buf_len,
struct slre_cap *caps, int num_caps, int flags);
/* Possible flags for slre_match() */
enum { SLRE_IGNORE_CASE = 1 };
/* slre_match() failure codes */
#define SLRE_NO_MATCH -1
#define SLRE_UNEXPECTED_QUANTIFIER -2
#define SLRE_UNBALANCED_BRACKETS -3
#define SLRE_INTERNAL_ERROR -4
#define SLRE_INVALID_CHARACTER_SET -5
#define SLRE_INVALID_METACHARACTER -6
#define SLRE_CAPS_ARRAY_TOO_SMALL -7
#define SLRE_TOO_MANY_BRANCHES -8
#define SLRE_TOO_MANY_BRACKETS -9
#ifdef __cplusplus
}
#endif
#endif /* SLRE_HEADER_DEFINED */