Compare commits

...

43 commits

Author SHA1 Message Date
Davide Depau 5524dd8af4 Always pretend cards are not vulnerable 2023-10-21 00:41:39 +02:00
Samuel Henrique ba072f16f6 update debian dir with up-to-date packaging 2018-07-24 11:45:40 +02:00
Philippe Teuwen dd0ce5caa6
Merge pull request #51 from iceman1001/master
define   #endif warning
2018-02-03 21:25:47 +01:00
Iceman 987c3dab3a
Merge pull request #1 from iceman1001/iceman1001-patch-1
chg:  remote faulty statement
2018-02-02 23:38:21 +01:00
Iceman 77db13ef90
chg: remote faulty statement
copy-paste error,  #endif
2018-02-02 23:38:08 +01:00
Romuald Conty 519e475b09
Merge pull request #50 from iceman1001/master
FIX:  warnings on gcc v7.2 ubuntu
2018-01-31 18:42:19 +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
user 593315f7d4 FIX: warning "DBG" redefine 2018-01-31 11:22:41 +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
Romuald Conty 290a075956 Bump version to 0.10.7 2013-12-06 15:30:30 +01:00
Romuald Conty 2fa70fb3d3 Use bzip2 instead of gzip compression algorithm 2013-12-06 15:25:56 +01:00
Romuald Conty e1a2b0225f Check if nfc_init() returns a valid context
This fixes a potential segfault when libnfc is not correctly initialized
2013-12-06 15:24:08 +01:00
Romuald Conty 222ba1838c Display right message when no tag is detected
This fixes a potential segfault due to an access to uninitialised memory variable access
2013-12-06 15:22:42 +01:00
17 changed files with 982 additions and 133 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

20
README.md Normal file
View file

@ -0,0 +1,20 @@
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
```
autoreconf -is
./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

@ -1,14 +1,14 @@
AC_INIT([mfoc],[0.10.6],[mifare@nethemba.com])
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
AM_INIT_AUTOMAKE(dist-bzip2 no-dist-gzip)
AC_PROG_CC
m4_ifdef([AM_SILENT_RULES],[AM_SILENT_RULES([yes])])

6
debian/.gitignore vendored
View file

@ -1,6 +0,0 @@
autoreconf.after
autoreconf.before
files
mfoc.debhelper.log
mfoc.substvars
mfoc/

68
debian/changelog vendored
View file

@ -1,17 +1,67 @@
mfoc (0.10.5-0) unstable; urgency=low
mfoc (0.10.7+git20180724-1) unstable; urgency=medium
* New upstream release
* New upstream version 0.10.7+git20180724
-- Romuald Conty <romuald@libnfc.org> Thu, 14 Feb 2013 21:16:42 +0100
-- Samuel Henrique <samueloph@debian.org> Tue, 24 Jul 2018 01:19:50 -0300
mfoc (0.10.4-0) unstable; urgency=low
mfoc (0.10.7+git20150512-0kali1) kali; urgency=medium
* New upstream release
* Import upstream (Closes: 0002240)
* Update debian files: watch, copyright
* Use debhelper 9
-- Romuald Conty <romuald@libnfc.org> Sun, 20 Jan 2013 15:58:42 +0100
-- Sophie Brun <sophie@freexian.com> Tue, 12 May 2015 12:05:24 +0200
mfoc (0.10.2pre3.1-0) unstable; urgency=low
mfoc (0.10.7-0kali2) kali; urgency=low
* Initial package
* Updated watch file
-- Thomas Hood <jdthood@gmail.com> Wed, 18 May 2011 12:00:00 +0200
-- Mati Aharoni <muts@kali.org> Sun, 12 Jan 2014 18:06:21 -0500
mfoc (0.10.7-0kali1) kali; urgency=low
* Upstream import
-- Mati Aharoni <muts@kali.org> Tue, 17 Dec 2013 09:12:38 -0500
mfoc (0.10.6-0kali0) kali; urgency=low
* Upstream import
-- Mati Aharoni <muts@kali.org> Mon, 19 Aug 2013 10:37:12 -0400
mfoc (0.10.5-0kali0) kali; urgency=low
* Upstream import.
-- Mati Aharoni <muts@kali.org> Sun, 24 Mar 2013 05:49:58 -0400
mfoc (0.10.3-1kali4) kali; urgency=low
* Removed desktop file
-- Mati Aharoni <muts@kali.org> Sat, 15 Dec 2012 14:23:37 -0500
mfoc (0.10.3-1kali3) kali; urgency=low
* Fixed compilation issue
-- Mati Aharoni <muts@kali.org> Tue, 04 Dec 2012 06:43:46 -0500
mfoc (0.10.3-1kali2) kali; urgency=low
* Version bump
-- Mati Aharoni <muts@kali.org> Sat, 01 Dec 2012 16:23:29 -0500
mfoc (0.10.3-1kali1) kali; urgency=low
* Version bump
-- Mati Aharoni <muts@kali.org> Sat, 01 Dec 2012 16:13:27 -0500
mfoc (0.10.3-1kali0) kali; urgency=low
* Initial release
-- Mati Aharoni <muts@kali.org> Sat, 01 Dec 2012 13:42:57 -0500

2
debian/compat vendored
View file

@ -1 +1 @@
9
11

21
debian/control vendored
View file

@ -1,17 +1,18 @@
Source: mfoc
Section: utils
Priority: extra
Maintainer: Thomas Hood <jdthood@gmail.com>
Build-Depends: debhelper (>=9), dh-autoreconf, libnfc-dev (>= 1.7.0~rc1), pkg-config
Standards-Version: 3.9.4
Homepage: http://code.google.com/p/mfoc/
Vcs-Git: http://code.google.com/p/mfoc/
Vcs-Browser: http://code.google.com/p/mfoc/source/browse/
Priority: optional
Maintainer: Debian Security Tools <team+pkg-security@tracker.debian.org>
Uploaders: Samuel Henrique <samueloph@debian.org>
Build-Depends: debhelper (>= 11), libnfc-dev, pkg-config
Standards-Version: 4.1.5
Homepage: https://github.com/nfc-tools/mfoc
Vcs-Browser: https://salsa.debian.org/pkg-security-team/mfoc
Vcs-Git: https://salsa.debian.org/pkg-security-team/mfoc.git
Package: mfoc
Architecture: any
Depends: ${shlibs:Depends}, ${misc:Depends}
Description: MIFARE Classic offline cracker
MFOC is an open source implementation of "offline nested" attack. It
helps to recove keys from MIFARE Classic tags and dump content to a
file.
This package includes the mfoc program which cracks the
encryption keys of the MIFARE Classic chip and dumps the
chip's memory contents to a file.

75
debian/copyright vendored
View file

@ -1,11 +1,59 @@
Format: http://dep.debian.net/deps/dep5
Upstream-Name: MFOC
Source: http://nfc-tools.googlecode.com/svn/trunk/mfoc
Source: https://github.com/nfc-tools/mfoc
Files: *
Copyright: 2009 Norbert Szetei, Pavol Luptak
2010 Micahal Boska, Romuald Conty
2011 Romuald Conty
Copyright: 2009 Norbert Szetei
2009 Pavol Luptak
2010 Micahal Boska
2010-2011 Romuald Conty <romuald@libnfc.org>
License: GPL-2+
Files: src/crypto1.c src/crapto1.c src/crapto1.h
Copyright: 2008-2009 bla <blapost@gmail.com>
License: GPL-2+
Files: src/slre.c src/slre.h
Copyright: 2013 Cesanta Software Limited
2004-2013 Sergey Lyubka <valenok@gmail.com>
License: GPL-2+
Files: src/nfc-utils.c src/mifare.c src/mifare.h src/nfc-utils.h
Copyright: 2010-2013 Philippe Teuwen
2009-2013 Romuald Conty <romuald@libnfc.org>
2009 Roel Verdult
2012-2013 Ludovic Rousseau <ludovic.rousseau@gmail.com>
2010-2012 Romain Tartière <romain.tartiere@gmail.com>
License: BSD-2-clause
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
.
1) Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
.
2 )Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
Files: debian/*
Copyright: 2011 Thomas Hood <jdthood@gmail.com>
2012-2014 Mati Aharoni <muts@kali.org>
2015 Sophie Brun <sophie@freexian.com
2018 Samuel Henrique <samueloph@debian.org>
License: GPL-2+
License: GPL-2+
This package is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@ -23,22 +71,3 @@ License: GPL-2+
On Debian systems, the complete text of the GNU General Public
License version 2 can be found in "/usr/share/common-licenses/GPL-2".
Files: debian/*
Copyright: 2011 Thomas Hood <jdthood@gmail.com>
License: GPL-2+
This package is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
.
This package is distributed in the hope that it will be useful,
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.
.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
.
On Debian systems, the complete text of the GNU General Public
License version 2 can be found in "/usr/share/common-licenses/GPL-2".

14
debian/rules vendored
View file

@ -1,16 +1,8 @@
#!/usr/bin/make -f
# -*- makefile -*-
# Sample debian/rules that uses debhelper.
# This file was originally written by Joey Hess and Craig Small.
# As a special exception, when this file is copied by dh-make into a
# dh-make output file, you may use that output file without restriction.
# This special exception was added by Craig Small in version 0.37 of dh-make.
export DEB_BUILD_MAINT_OPTIONS = hardening=+all
# Uncomment this to turn on verbose mode.
#export DH_VERBOSE=1
%:
dh $@
override_dh_installchangelogs:
dh_installchangelogs ChangeLog
%:
dh $@ --with autoreconf

8
debian/watch vendored
View file

@ -1,7 +1,3 @@
# See uscan(1) for format
# Compulsory line, this is a version 3 file
version=3
http://code.google.com/p/mfoc/downloads/list .*/mfoc-(.*).tar.gz
version=4
https://github.com/nfc-tools/mfoc/tags/ .*/mfoc-(.*)\.tar\.gz

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;
@ -83,43 +96,55 @@ int main(int argc, char *const argv[])
uint8_t defaultKeys[][6] = {
{0xff, 0xff, 0xff, 0xff, 0xff, 0xff}, // Default key (first key used by program if no user defined key)
{0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5}, // NFCForum MAD key
{0xd3, 0xf7, 0xd3, 0xf7, 0xd3, 0xf7}, // NFCForum content key
{0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // Blank key
{0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5},
{0x4d, 0x3a, 0x99, 0xc3, 0x51, 0xdd},
{0x1a, 0x98, 0x2c, 0x7e, 0x45, 0x9a},
{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff},
{0x71, 0x4c, 0x5c, 0x88, 0x6e, 0x97},
{0x58, 0x7e, 0xe5, 0xf9, 0x35, 0x0f},
{0xa0, 0x47, 0x8c, 0xc3, 0x90, 0x91},
{0x53, 0x3c, 0xb6, 0xc7, 0x23, 0xf6},
{0x8f, 0xd0, 0xa4, 0xf2, 0x56, 0xe9}
// {0xd3, 0xf7, 0xd3, 0xf7, 0xd3, 0xf7}, // NFCForum content key
// {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // Blank key
// {0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5},
// {0x4d, 0x3a, 0x99, 0xc3, 0x51, 0xdd},
// {0x1a, 0x98, 0x2c, 0x7e, 0x45, 0x9a},
// {0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff},
// {0x71, 0x4c, 0x5c, 0x88, 0x6e, 0x97},
// {0x58, 0x7e, 0xe5, 0xf9, 0x35, 0x0f},
// {0xa0, 0x47, 0x8c, 0xc3, 0x90, 0x91},
// {0x53, 0x3c, 0xb6, 0xc7, 0x23, 0xf6},
// {0x8f, 0xd0, 0xa4, 0xf2, 0x56, 0xe9}
};
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
if (!(probes = atoi(optarg)) || probes < 1) {
probes = atoi(optarg);
if (probes < 0) {
ERR("The number of probes must be a positive number");
exit(EXIT_FAILURE);
}
@ -136,6 +161,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 +208,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,28 +258,58 @@ 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);
*/
// mf_select_tag(r.pdi, &(t.nt));
if (nfc_initiator_select_passive_target(r.pdi, nm, NULL, 0, &t.nt) < 0) {
int tag_count;
if ((tag_count = nfc_initiator_select_passive_target(r.pdi, nm, NULL, 0, &t.nt)) < 0) {
nfc_perror(r.pdi, "nfc_initiator_select_passive_target");
goto error;
} else if (tag_count == 0) {
ERR("No tag found.");
goto error;
}
// 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) {
@ -261,7 +349,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);
@ -288,8 +376,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;
@ -325,9 +444,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);
@ -356,15 +494,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);
@ -377,7 +541,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);
@ -416,7 +591,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;
@ -425,8 +600,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;
@ -527,7 +728,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;
@ -554,20 +756,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);
@ -579,6 +784,10 @@ void mf_init(mfreader *r)
{
// Connect to the first NFC device
nfc_init(&context);
if (context == NULL) {
ERR("Unable to init libnfc (malloc)");
exit(EXIT_FAILURE);
}
r->pdi = nfc_open(context, NULL);
if (!r->pdi) {
printf("No NFC device found.\n");
@ -620,11 +829,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);
@ -667,10 +871,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");
@ -678,6 +878,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%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;
@ -817,9 +1059,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 (true || ! 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
@ -16,7 +30,7 @@
#define DEFAULT_DIST_NR 15
// Default number of probes for a key recovery for one sector
#define DEFAULT_PROBES_NR 150
#define DEFAULT_PROBES_NR 1
// Number of sets with 32b keys
#define DEFAULT_SETS_NR 5
@ -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);

437
src/slre.c Normal file
View file

@ -0,0 +1,437 @@
/*
* 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
# ifndef DBG
# define DBG(x) printf x
# endif
#else
# ifndef DBG
# define DBG(x)
# endif
#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 */