From 1fa97ee719a97bbaceeec56381a5b3ef252a2564 Mon Sep 17 00:00:00 2001 From: Astra <93453568+Astrrra@users.noreply.github.com> Date: Wed, 3 Apr 2024 20:32:40 +0900 Subject: [PATCH 1/5] [FL-3357] Magic Gen2 support (#143) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- nfc_magic/.catalog/changelog.md | 3 + nfc_magic/.catalog/screenshots/0.png | Bin 0 -> 1528 bytes nfc_magic/.catalog/screenshots/1.png | Bin 1568 -> 1790 bytes nfc_magic/.catalog/screenshots/2.png | Bin 2152 -> 1473 bytes nfc_magic/application.fam | 7 +- nfc_magic/assets/DolphinSuccess_91x55.png | Bin 0 -> 930 bytes nfc_magic/assets/WarningDolphinFlip_45x42.png | Bin 0 -> 1437 bytes nfc_magic/{lib => }/magic/nfc_magic_scanner.c | 38 +- nfc_magic/{lib => }/magic/nfc_magic_scanner.h | 0 .../magic/protocols/gen1a/gen1a_poller.c | 0 .../magic/protocols/gen1a/gen1a_poller.h | 0 .../magic/protocols/gen1a/gen1a_poller_i.c | 0 .../magic/protocols/gen1a/gen1a_poller_i.h | 0 nfc_magic/magic/protocols/gen2/crypto1.c | 178 +++++ nfc_magic/magic/protocols/gen2/crypto1.h | 45 ++ nfc_magic/magic/protocols/gen2/gen2_poller.c | 594 +++++++++++++++++ nfc_magic/magic/protocols/gen2/gen2_poller.h | 97 +++ .../magic/protocols/gen2/gen2_poller_i.c | 629 ++++++++++++++++++ .../magic/protocols/gen2/gen2_poller_i.h | 139 ++++ .../magic/protocols/gen4/gen4_poller.c | 1 + .../magic/protocols/gen4/gen4_poller.h | 0 .../magic/protocols/gen4/gen4_poller_i.c | 0 .../magic/protocols/gen4/gen4_poller_i.h | 0 .../magic/protocols/nfc_magic_protocols.c | 6 +- .../magic/protocols/nfc_magic_protocols.h | 2 + nfc_magic/nfc_magic_app.c | 32 +- nfc_magic/nfc_magic_app_i.h | 56 +- nfc_magic/scenes/nfc_magic_scene_config.h | 5 + .../scenes/nfc_magic_scene_file_select.c | 16 +- nfc_magic/scenes/nfc_magic_scene_gen2_menu.c | 55 ++ .../scenes/nfc_magic_scene_gen2_write_check.c | 130 ++++ nfc_magic/scenes/nfc_magic_scene_magic_info.c | 35 +- .../nfc_magic_scene_mf_classic_dict_attack.c | 311 +++++++++ .../scenes/nfc_magic_scene_mf_classic_menu.c | 65 ++ .../nfc_magic_scene_mf_classic_write_check.c | 124 ++++ nfc_magic/scenes/nfc_magic_scene_success.c | 4 +- nfc_magic/scenes/nfc_magic_scene_wipe.c | 54 +- nfc_magic/scenes/nfc_magic_scene_wipe_fail.c | 7 +- nfc_magic/scenes/nfc_magic_scene_write.c | 55 +- nfc_magic/scenes/nfc_magic_scene_write_fail.c | 20 +- nfc_magic/views/dict_attack.c | 245 +++++++ nfc_magic/views/dict_attack.h | 50 ++ nfc_magic/views/write_problems.c | 160 +++++ nfc_magic/views/write_problems.h | 32 + 44 files changed, 3127 insertions(+), 68 deletions(-) create mode 100644 nfc_magic/.catalog/screenshots/0.png create mode 100644 nfc_magic/assets/DolphinSuccess_91x55.png create mode 100644 nfc_magic/assets/WarningDolphinFlip_45x42.png rename nfc_magic/{lib => }/magic/nfc_magic_scanner.c (75%) rename nfc_magic/{lib => }/magic/nfc_magic_scanner.h (100%) rename nfc_magic/{lib => }/magic/protocols/gen1a/gen1a_poller.c (100%) rename nfc_magic/{lib => }/magic/protocols/gen1a/gen1a_poller.h (100%) rename nfc_magic/{lib => }/magic/protocols/gen1a/gen1a_poller_i.c (100%) rename nfc_magic/{lib => }/magic/protocols/gen1a/gen1a_poller_i.h (100%) create mode 100644 nfc_magic/magic/protocols/gen2/crypto1.c create mode 100644 nfc_magic/magic/protocols/gen2/crypto1.h create mode 100644 nfc_magic/magic/protocols/gen2/gen2_poller.c create mode 100644 nfc_magic/magic/protocols/gen2/gen2_poller.h create mode 100644 nfc_magic/magic/protocols/gen2/gen2_poller_i.c create mode 100644 nfc_magic/magic/protocols/gen2/gen2_poller_i.h rename nfc_magic/{lib => }/magic/protocols/gen4/gen4_poller.c (99%) rename nfc_magic/{lib => }/magic/protocols/gen4/gen4_poller.h (100%) rename nfc_magic/{lib => }/magic/protocols/gen4/gen4_poller_i.c (100%) rename nfc_magic/{lib => }/magic/protocols/gen4/gen4_poller_i.h (100%) rename nfc_magic/{lib => }/magic/protocols/nfc_magic_protocols.c (64%) rename nfc_magic/{lib => }/magic/protocols/nfc_magic_protocols.h (75%) create mode 100644 nfc_magic/scenes/nfc_magic_scene_gen2_menu.c create mode 100644 nfc_magic/scenes/nfc_magic_scene_gen2_write_check.c create mode 100644 nfc_magic/scenes/nfc_magic_scene_mf_classic_dict_attack.c create mode 100644 nfc_magic/scenes/nfc_magic_scene_mf_classic_menu.c create mode 100644 nfc_magic/scenes/nfc_magic_scene_mf_classic_write_check.c create mode 100644 nfc_magic/views/dict_attack.c create mode 100644 nfc_magic/views/dict_attack.h create mode 100644 nfc_magic/views/write_problems.c create mode 100644 nfc_magic/views/write_problems.h diff --git a/nfc_magic/.catalog/changelog.md b/nfc_magic/.catalog/changelog.md index 616a170c..bc6c6091 100644 --- a/nfc_magic/.catalog/changelog.md +++ b/nfc_magic/.catalog/changelog.md @@ -1,3 +1,6 @@ +## 1.7 + - Gen2/CUID write support + ## 1.6 - Rework with new bit lib API diff --git a/nfc_magic/.catalog/screenshots/0.png b/nfc_magic/.catalog/screenshots/0.png new file mode 100644 index 0000000000000000000000000000000000000000..f18491651080fa3afe9feab1330025ec22e3af59 GIT binary patch literal 1528 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 MPgg&ebxsLQ05`TTF8}}l diff --git a/nfc_magic/.catalog/screenshots/2.png b/nfc_magic/.catalog/screenshots/2.png index 9bb578f3e1016f42256ffe1f357ba9ecbee4361a..16976fe0db951114fbeb906aadc53cbbdef61c00 100644 GIT binary patch literal 1473 zcmds1T}%^M6h6}~TfxQR53VSb#;i&-*_v$#V(5YzdDjAfm9()hX$H|OSjC+B|W zyWhPh|4iN-9JDkD0D=<};!^>5^cI0ROh}qYUrUF;;)HY!z}$t*Lf`_r1Ry|@7{6i1 zA#~J{Qgbn3k+QF3py%PomJ^u7eR4QYp1HIyc-vk+g_;uvA2<@5PEFjeHJhtu__%3$ z%Ik7xtBOT{o(6~y1m%Fm6KTto@<>zesACuMC3lvFWY2XxsBk{6)W-rWrVE+mw}P%h z*R`IYuBNpMIig>_EjJZ=M25+BqE`0eA9D3{ z2D$GqznToSNW=DQB`J(cCX0OO0+eKXPC1S zql)(H(|ZwE8{)s8^S>r9`k_?UgSXHcfNeuHRpnZm~-AcIg7gy&ypr_FwZdrtWj>MgiCVHlNf z7RE^hfGhoMyD4Pr!AX||x%~$x84kF$jt^O4zg9Uy5OYk^ZPG*0qi_Je-(XSn<4WPk zO3&~EaAUp(w%E_-Mi_=~0hY(QqqHs-?4OW(7WO2@%2=H+ttHo&_4@FL40g7zEAzha z0QnnjX2!a|8wAY{=2O2O5H2U*aCB~eBEm%l!-3G$#kPymf^R&mifrN(mWP*&YON-3 z>+>ong+MRo0hV@&@HK6F6u661e?TJEU-akze)*4qf)-1AX=E mZxBm6fwdkZ=Km6ItU}O7*H+e6-#tqI$&k1yIlgIQ){)Q9d*EFF literal 2152 zcma)8eN~# zocmo|Y>cl@fDZt`cf-X+rAkHJ}q zCBz?<#eYNu;HYHOQk2)l#v8=O1O6|>8eibt<6b@E#rp;SFCBw_qbR$$7GW#g84%gr zZtE+4m0P3pMqoCdnqkcwhD}K#BkRCz8p#z*5R=$?u}@PgdF-e(3?{(TzjG;6-RU9G zz?s3*Qws*X?J_#fB4=`S2^k8OGJ5=_q5HHgn4US5?c1a)SeA1PGT%y05QS^5V|KDg zb9;$Hh$rB)-;qCww%*VGK^)HOSGfhZ?vzA6#H)(GI7`jp-{ZCkHKQVob|j1$j3=%HLO(-lrRg1E*hp5a>ZBf?ElRu`&DuWMKd)u)7Q zHq%p^GZXuDV;dklz#+oUdlC~TK5m~d0gye@Vbun+JLt|EPJJyG)e~`fFQtOBeAppW zT?#D6iTkHJ@ETVO2=hA#%IKMnI!^@4Gwebr*z7yB(hoeHJ5y^;50U+raEXONPhEw> z;e0<<#)L-&d%EdQU-66p;Fo7;oTxlay(7ugxp8VzkTcHnO5pC_BYE({>7M}rjrJ~tYUFoS@___qfJHYet=J=2&P_As~|}&q4kG{)X>N`9jQW_N%o6D;E?h}&JbyU64L9Q+5|o{1>OG4)uV=u^Tmzn#HBpdEUoPyCg{S26W};G_ zAXIc_|1e#OnX)WCRkG2xBQC5fH;oVnw^r6qznO0eTggaCN4nPM-zJ|OG2BuX{(+N32d$RR|C-G_3y0h z9b-l~Gua^}U0oqfFtNq*<8}c20%xi*0d^DKB#rpDd7GER(fi3chJ1nT^LRV6v;E5b za{hvs;R!910x0fkWWxsQ-m=Mhs^}YKgas;+i}9$ aiHU4A<$Zna@6KNbupufovSwY{SN{N){bA1l diff --git a/nfc_magic/application.fam b/nfc_magic/application.fam index a00b4b48..68dfead9 100644 --- a/nfc_magic/application.fam +++ b/nfc_magic/application.fam @@ -10,13 +10,8 @@ App( ], stack_size=4 * 1024, fap_description="Application for writing to NFC tags with modifiable sector 0", - fap_version="1.6", + fap_version="1.7", fap_icon="assets/125_10px.png", fap_category="NFC", - fap_private_libs=[ - Lib( - name="magic", - ), - ], fap_icon_assets="assets", ) diff --git a/nfc_magic/assets/DolphinSuccess_91x55.png b/nfc_magic/assets/DolphinSuccess_91x55.png new file mode 100644 index 0000000000000000000000000000000000000000..80caeb203c69add5e5786f576daeb3b018b1c859 GIT binary patch literal 930 zcmV;T16}-yP)a80tk`0v%FYt{QS^;XijtOsVUD?#;+`c<*fIT3&TD?M& z)R06pD<=<1yq5Nwf~f2rMN`RW@xLABnmu1zf@(zLuCU@6K;_KkNePA;OOnm`YL5b? zN2t4s&U7U^vAMgIRkqiKc*{aXbz{Ogp;&7-OLmqEf$vo$7-RLCrSa?mNx6`y!;Df( z6!Bb~-MqCLF2$eY9%M_3j&u!?2c+eqEmo5o!--5QA2=SVY);XUHf@(^tE%uA=^F4| zrp1NhD1IK~*dB?Q}$eEWlXi^hqK5WNV0cpw&j+> z2}`joYeqoo2_oW=Wn?b2tL)VhkwI*PJBi9IMcFu3E*WX;%y99JSV~55t#A0nDaB}EWW!Q%O4%8>eTYkN)$EF$q_;L(K*hAa z2hqnE>{mV&?4AV?IYvM6qu{F96*eI9?jk+0;R}QLx7otB*2Mo*dJDlBjSja7cs(mO zYRx|7yE#C=O?`f{(cwHl_R}0xpMA@nWNxI#8b;jNA{Bje*34|Bd+T}@ zRAnDY!qFg5b3OC9n9-_k4Ou^H=NP>axIysFt&S4Pu7oUAw`Qo>^L_aHKYUj9gKVu+ z{C1^dkwxQ=>|32;6>xX8G)K>z>% literal 0 HcmV?d00001 diff --git a/nfc_magic/assets/WarningDolphinFlip_45x42.png b/nfc_magic/assets/WarningDolphinFlip_45x42.png new file mode 100644 index 0000000000000000000000000000000000000000..2ba54afce0249303787fb3d94a78cc167a81a253 GIT binary patch literal 1437 zcmeAS@N?(olHy`uVBq!ia0vp^x+tIX_n~5u`@1BDVmjn}NZ`zM>#8IXksPAt^OIGtXA( z{qFrr3YjUkO5vuy2EGN(sTr9bRYj@6RemAKRoTgwDN6Qs3N{s1Km&49OA-|-a&z*E zttxDlz~)*3*&tzkB?YjOl5ATgh@&EW0~DO|i&7QL^$c~B4Gatv%q{g&Qxc7mjMEa6 zbrg&Yj12V+fyi9f(A>(%*vimS0Sc6W78a$XSp~VcL9GMwY?U%fN(!v>^~=l4^~#O) z@{7{-4J|D#^$m>ljf`}GDs+o0^GXscbn}XpVJ5hw7AF^F7L;V>=P7_pOiaozEwNPs zIu_!K+yY-;xWReF(69oAntnxMfxe-hfqrf-$ZKHL#U(+h2xnkbT^v$bkg6Y)TAW{6 zlnjiLG-a4(VDRC$2&53`8Y`Fl?$S+VZGS)Lx(C|%6&ddXeXo5l)>e$qx%(B!Jx1#)91#s|KWnyuHfvcmlo299R znSrq(*!kwBrk1WoW~Q!gE(Qk1mP$~)DOkJ?)oY1UuRhQ*`k=T)iffn@Wcz` zz>|M!9x%-p0TcJDoOQE-Iq{~ai(^QH`_*ZU>zWmKTucA|KmWe+?mErbXs(XSlV`YU zyj{3y=hyt?pBvV_R?6ew^D+M8uNu3qtmT_y-D0L)TyCc0dsA%UJkv=5Rzh;RJXOUd zb2IbOTqF$GpKcbJdrqLVzGd1T*X2+9*4{lY#C9V4>K2V9OE2i`>{wIr%jf)?BJJZd zuUTJdQogkH&a9}lvA4LA6npx)qIWK?x%%_%t!Ix}6uLep6h2dsS$*~Ev(@QR+>tKg z{Rx5VCNfp6n;I+CU0M0%6tiXxoBrH_wi@r8w!N3b)U#YQ?77X3J+inwuOh!a;>s!M zv#A>UI^rh96)w2m$Hn}af3o_`pcBH`g5Q$P&bQHDxm}W5pZ!?$r-&Js9`F?M>z7Y^ zY$~^~+n{bs1@D!OSqrT8+&A~yr*>6)e(dx~*IxLhbfzrnPjRGZ^@Z|QqP|R zTf{vU*uQ$uw_jB|PcZzAILGiaHBNU)cG13w&3AXCSuJ$kaL2{#H7|p$nykgUeK8Iy zEju&pn3Sd&9GD@pm18L<>(i^uuF=;{N93ug){1WBn;h+#e&AT0TJLc_#>&d$ZvF9E zzJWrXr!3AsoxANwWh&>=#qMTJV%skmUV2;@?^2MR`M%R;)nyx{?bH9g`?l#1bH%|O Uzg^}hzX270p00i_>zopr0LnWHWB>pF literal 0 HcmV?d00001 diff --git a/nfc_magic/lib/magic/nfc_magic_scanner.c b/nfc_magic/magic/nfc_magic_scanner.c similarity index 75% rename from nfc_magic/lib/magic/nfc_magic_scanner.c rename to nfc_magic/magic/nfc_magic_scanner.c index 0d54e721..77be4e11 100644 --- a/nfc_magic/lib/magic/nfc_magic_scanner.c +++ b/nfc_magic/magic/nfc_magic_scanner.c @@ -1,6 +1,7 @@ #include "nfc_magic_scanner.h" #include "protocols/gen1a/gen1a_poller.h" +#include "protocols/gen2/gen2_poller.h" #include "protocols/gen4/gen4_poller.h" #include @@ -65,20 +66,33 @@ static int32_t nfc_magic_scanner_worker(void* context) { furi_assert(instance->session_state == NfcMagicScannerSessionStateActive); while(instance->session_state == NfcMagicScannerSessionStateActive) { - if(instance->current_protocol == NfcMagicProtocolGen1) { - instance->magic_protocol_detected = gen1a_poller_detect(instance->nfc); - } else if(instance->current_protocol == NfcMagicProtocolGen4) { - Gen4PollerError error = gen4_poller_detect(instance->nfc, instance->gen4_password); - if(error == Gen4PollerErrorProtocol) { - NfcMagicScannerEvent event = { - .type = NfcMagicScannerEventTypeDetectedNotMagic, - }; - instance->callback(event, instance->context); - break; - } else { + do { + if(instance->current_protocol == NfcMagicProtocolGen1) { + instance->magic_protocol_detected = gen1a_poller_detect(instance->nfc); + if(instance->magic_protocol_detected) { + break; + } + } else if(instance->current_protocol == NfcMagicProtocolGen4) { + Gen4PollerError error = gen4_poller_detect(instance->nfc, instance->gen4_password); instance->magic_protocol_detected = (error == Gen4PollerErrorNone); + if(instance->magic_protocol_detected) { + break; + } + } else if(instance->current_protocol == NfcMagicProtocolGen2) { + Gen2PollerError error = gen2_poller_detect(instance->nfc); + instance->magic_protocol_detected = (error == Gen2PollerErrorNone); + if(instance->magic_protocol_detected) { + break; + } + } else if(instance->current_protocol == NfcMagicProtocolClassic) { + NfcPoller* poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfClassic); + instance->magic_protocol_detected = nfc_poller_detect(poller); + nfc_poller_free(poller); + if(instance->magic_protocol_detected) { + break; + } } - } + } while(false); if(instance->magic_protocol_detected) { NfcMagicScannerEvent event = { diff --git a/nfc_magic/lib/magic/nfc_magic_scanner.h b/nfc_magic/magic/nfc_magic_scanner.h similarity index 100% rename from nfc_magic/lib/magic/nfc_magic_scanner.h rename to nfc_magic/magic/nfc_magic_scanner.h diff --git a/nfc_magic/lib/magic/protocols/gen1a/gen1a_poller.c b/nfc_magic/magic/protocols/gen1a/gen1a_poller.c similarity index 100% rename from nfc_magic/lib/magic/protocols/gen1a/gen1a_poller.c rename to nfc_magic/magic/protocols/gen1a/gen1a_poller.c diff --git a/nfc_magic/lib/magic/protocols/gen1a/gen1a_poller.h b/nfc_magic/magic/protocols/gen1a/gen1a_poller.h similarity index 100% rename from nfc_magic/lib/magic/protocols/gen1a/gen1a_poller.h rename to nfc_magic/magic/protocols/gen1a/gen1a_poller.h diff --git a/nfc_magic/lib/magic/protocols/gen1a/gen1a_poller_i.c b/nfc_magic/magic/protocols/gen1a/gen1a_poller_i.c similarity index 100% rename from nfc_magic/lib/magic/protocols/gen1a/gen1a_poller_i.c rename to nfc_magic/magic/protocols/gen1a/gen1a_poller_i.c diff --git a/nfc_magic/lib/magic/protocols/gen1a/gen1a_poller_i.h b/nfc_magic/magic/protocols/gen1a/gen1a_poller_i.h similarity index 100% rename from nfc_magic/lib/magic/protocols/gen1a/gen1a_poller_i.h rename to nfc_magic/magic/protocols/gen1a/gen1a_poller_i.h diff --git a/nfc_magic/magic/protocols/gen2/crypto1.c b/nfc_magic/magic/protocols/gen2/crypto1.c new file mode 100644 index 00000000..2ac2d771 --- /dev/null +++ b/nfc_magic/magic/protocols/gen2/crypto1.c @@ -0,0 +1,178 @@ +#include "crypto1.h" + +#include +#include +#include + +// Algorithm from https://github.com/RfidResearchGroup/proxmark3.git + +#define SWAPENDIAN(x) \ + ((x) = ((x) >> 8 & 0xff00ff) | ((x) & 0xff00ff) << 8, (x) = (x) >> 16 | (x) << 16) +#define LF_POLY_ODD (0x29CE5C) +#define LF_POLY_EVEN (0x870804) + +#define BEBIT(x, n) FURI_BIT(x, (n) ^ 24) + +Crypto1* crypto1_alloc() { + Crypto1* instance = malloc(sizeof(Crypto1)); + + return instance; +} + +void crypto1_free(Crypto1* instance) { + furi_assert(instance); + + free(instance); +} + +void crypto1_reset(Crypto1* crypto1) { + furi_assert(crypto1); + crypto1->even = 0; + crypto1->odd = 0; +} + +void crypto1_init(Crypto1* crypto1, uint64_t key) { + furi_assert(crypto1); + crypto1->even = 0; + crypto1->odd = 0; + for(int8_t i = 47; i > 0; i -= 2) { + crypto1->odd = crypto1->odd << 1 | FURI_BIT(key, (i - 1) ^ 7); + crypto1->even = crypto1->even << 1 | FURI_BIT(key, i ^ 7); + } +} + +static uint32_t crypto1_filter(uint32_t in) { + uint32_t out = 0; + out = 0xf22c0 >> (in & 0xf) & 16; + out |= 0x6c9c0 >> (in >> 4 & 0xf) & 8; + out |= 0x3c8b0 >> (in >> 8 & 0xf) & 4; + out |= 0x1e458 >> (in >> 12 & 0xf) & 2; + out |= 0x0d938 >> (in >> 16 & 0xf) & 1; + return FURI_BIT(0xEC57E80A, out); +} + +uint8_t crypto1_bit(Crypto1* crypto1, uint8_t in, int is_encrypted) { + furi_assert(crypto1); + uint8_t out = crypto1_filter(crypto1->odd); + uint32_t feed = out & (!!is_encrypted); + feed ^= !!in; + feed ^= LF_POLY_ODD & crypto1->odd; + feed ^= LF_POLY_EVEN & crypto1->even; + crypto1->even = crypto1->even << 1 | (nfc_util_even_parity32(feed)); + + FURI_SWAP(crypto1->odd, crypto1->even); + return out; +} + +uint8_t crypto1_byte(Crypto1* crypto1, uint8_t in, int is_encrypted) { + furi_assert(crypto1); + uint8_t out = 0; + for(uint8_t i = 0; i < 8; i++) { + out |= crypto1_bit(crypto1, FURI_BIT(in, i), is_encrypted) << i; + } + return out; +} + +uint32_t crypto1_word(Crypto1* crypto1, uint32_t in, int is_encrypted) { + furi_assert(crypto1); + uint32_t out = 0; + for(uint8_t i = 0; i < 32; i++) { + out |= (uint32_t)crypto1_bit(crypto1, BEBIT(in, i), is_encrypted) << (24 ^ i); + } + return out; +} + +uint32_t prng_successor(uint32_t x, uint32_t n) { + SWAPENDIAN(x); + while(n--) x = x >> 1 | (x >> 16 ^ x >> 18 ^ x >> 19 ^ x >> 21) << 31; + + return SWAPENDIAN(x); +} + +void crypto1_decrypt(Crypto1* crypto, const BitBuffer* buff, BitBuffer* out) { + furi_assert(crypto); + furi_assert(buff); + furi_assert(out); + + size_t bits = bit_buffer_get_size(buff); + bit_buffer_set_size(out, bits); + const uint8_t* encrypted_data = bit_buffer_get_data(buff); + if(bits < 8) { + uint8_t decrypted_byte = 0; + uint8_t encrypted_byte = encrypted_data[0]; + decrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(encrypted_byte, 0)) << 0; + decrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(encrypted_byte, 1)) << 1; + decrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(encrypted_byte, 2)) << 2; + decrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(encrypted_byte, 3)) << 3; + bit_buffer_set_byte(out, 0, decrypted_byte); + } else { + for(size_t i = 0; i < bits / 8; i++) { + uint8_t decrypted_byte = crypto1_byte(crypto, 0, 0) ^ encrypted_data[i]; + bit_buffer_set_byte(out, i, decrypted_byte); + } + } +} + +void crypto1_encrypt(Crypto1* crypto, uint8_t* keystream, const BitBuffer* buff, BitBuffer* out) { + furi_assert(crypto); + furi_assert(buff); + furi_assert(out); + + size_t bits = bit_buffer_get_size(buff); + bit_buffer_set_size(out, bits); + const uint8_t* plain_data = bit_buffer_get_data(buff); + if(bits < 8) { + uint8_t encrypted_byte = 0; + for(size_t i = 0; i < bits; i++) { + encrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(plain_data[0], i)) << i; + } + bit_buffer_set_byte(out, 0, encrypted_byte); + } else { + for(size_t i = 0; i < bits / 8; i++) { + uint8_t encrypted_byte = crypto1_byte(crypto, keystream ? keystream[i] : 0, 0) ^ + plain_data[i]; + bool parity_bit = + ((crypto1_filter(crypto->odd) ^ nfc_util_odd_parity8(plain_data[i])) & 0x01); + bit_buffer_set_byte_with_parity(out, i, encrypted_byte, parity_bit); + } + } +} + +void crypto1_encrypt_reader_nonce( + Crypto1* crypto, + uint64_t key, + uint32_t cuid, + uint8_t* nt, + uint8_t* nr, + BitBuffer* out, + bool is_nested) { + furi_assert(crypto); + furi_assert(nt); + furi_assert(nr); + furi_assert(out); + + bit_buffer_set_size_bytes(out, 8); + uint32_t nt_num = bit_lib_bytes_to_num_be(nt, sizeof(uint32_t)); + + crypto1_init(crypto, key); + if(is_nested) { + nt_num = crypto1_word(crypto, nt_num ^ cuid, 1) ^ nt_num; + } else { + crypto1_word(crypto, nt_num ^ cuid, 0); + } + + for(size_t i = 0; i < 4; i++) { + uint8_t byte = crypto1_byte(crypto, nr[i], 0) ^ nr[i]; + bool parity_bit = ((crypto1_filter(crypto->odd) ^ nfc_util_odd_parity8(nr[i])) & 0x01); + bit_buffer_set_byte_with_parity(out, i, byte, parity_bit); + nr[i] = byte; + } + + nt_num = prng_successor(nt_num, 32); + for(size_t i = 4; i < 8; i++) { + nt_num = prng_successor(nt_num, 8); + uint8_t byte = crypto1_byte(crypto, 0, 0) ^ (uint8_t)(nt_num); + bool parity_bit = ((crypto1_filter(crypto->odd) ^ nfc_util_odd_parity8(nt_num)) & 0x01); + bit_buffer_set_byte_with_parity(out, i, byte, parity_bit); + } +} diff --git a/nfc_magic/magic/protocols/gen2/crypto1.h b/nfc_magic/magic/protocols/gen2/crypto1.h new file mode 100644 index 00000000..7cc16fcf --- /dev/null +++ b/nfc_magic/magic/protocols/gen2/crypto1.h @@ -0,0 +1,45 @@ +#pragma once + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct { + uint32_t odd; + uint32_t even; +} Crypto1; + +Crypto1* crypto1_alloc(); + +void crypto1_free(Crypto1* instance); + +void crypto1_reset(Crypto1* crypto1); + +void crypto1_init(Crypto1* crypto1, uint64_t key); + +uint8_t crypto1_bit(Crypto1* crypto1, uint8_t in, int is_encrypted); + +uint8_t crypto1_byte(Crypto1* crypto1, uint8_t in, int is_encrypted); + +uint32_t crypto1_word(Crypto1* crypto1, uint32_t in, int is_encrypted); + +void crypto1_decrypt(Crypto1* crypto, const BitBuffer* buff, BitBuffer* out); + +void crypto1_encrypt(Crypto1* crypto, uint8_t* keystream, const BitBuffer* buff, BitBuffer* out); + +void crypto1_encrypt_reader_nonce( + Crypto1* crypto, + uint64_t key, + uint32_t cuid, + uint8_t* nt, + uint8_t* nr, + BitBuffer* out, + bool is_nested); + +uint32_t prng_successor(uint32_t x, uint32_t n); + +#ifdef __cplusplus +} +#endif diff --git a/nfc_magic/magic/protocols/gen2/gen2_poller.c b/nfc_magic/magic/protocols/gen2/gen2_poller.c new file mode 100644 index 00000000..18c043f1 --- /dev/null +++ b/nfc_magic/magic/protocols/gen2/gen2_poller.c @@ -0,0 +1,594 @@ +#include "gen2_poller_i.h" +#include + +#include + +#define GEN2_POLLER_THREAD_FLAG_DETECTED (1U << 0) + +#define TAG "GEN2" + +typedef NfcCommand (*Gen2PollerStateHandler)(Gen2Poller* instance); + +typedef struct { + NfcPoller* poller; + BitBuffer* tx_buffer; + BitBuffer* rx_buffer; + FuriThreadId thread_id; + bool detected; + Gen2PollerError error; +} Gen2PollerDetectContext; + +// Array of known Gen2 ATS responses +// 0978009102DABC1910F005 - flavour 2 +// 0978009102DABC1910F005 - flavour 4 +// 0D780071028849A13020150608563D - flavour 6 +// Other flavours can't be detected other than by just trying to write to block 0 +const uint8_t GEN2_ATS[3][16] = { + {0x09, 0x78, 0x00, 0x91, 0x02, 0xDA, 0xBC, 0x19, 0x10, 0xF0, 0x05}, + {0x09, 0x78, 0x00, 0x91, 0x02, 0xDA, 0xBC, 0x19, 0x10, 0xF0, 0x05}, + {0x0D, 0x78, 0x00, 0x71, 0x02, 0x88, 0x49, 0xA1, 0x30, 0x20, 0x15, 0x06, 0x08, 0x56, 0x3D}}; + +static const MfClassicBlock gen2_poller_default_block_0 = { + .data = + {0x00, + 0x01, + 0x02, + 0x03, + 0x00, // BCC - IMPORTANT + 0x08, // SAK + 0x04, // ATQA0 + 0x00, // ATQA1 + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00}, +}; + +static const MfClassicBlock gen2_poller_default_empty_block = { + .data = + {0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00, + 0x00}, +}; + +static MfClassicBlock gen2_poller_default_sector_trailer_block = { + .data = + {0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0x07, + 0x80, + 0x69, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF, + 0xFF}, +}; + +const char* const gen2_problem_strings[] = { + "UID may be non-\nrewritable. Check data after writing", + "No data in selected file", + "Some sectors are locked", + "Can't find keys to some sectors", + "The selected file is incomplete", +}; + +Gen2Poller* gen2_poller_alloc(Nfc* nfc) { + Gen2Poller* instance = malloc(sizeof(Gen2Poller)); + instance->poller = nfc_poller_alloc(nfc, NfcProtocolIso14443_3a); + instance->data = mf_classic_alloc(); + instance->crypto = crypto1_alloc(); + instance->tx_plain_buffer = bit_buffer_alloc(GEN2_POLLER_MAX_BUFFER_SIZE); + instance->tx_encrypted_buffer = bit_buffer_alloc(GEN2_POLLER_MAX_BUFFER_SIZE); + instance->rx_plain_buffer = bit_buffer_alloc(GEN2_POLLER_MAX_BUFFER_SIZE); + instance->rx_encrypted_buffer = bit_buffer_alloc(GEN2_POLLER_MAX_BUFFER_SIZE); + instance->card_state = Gen2CardStateLost; + + instance->gen2_event.data = &instance->gen2_event_data; + + instance->mode_ctx.write_ctx.mfc_data_source = malloc(sizeof(MfClassicData)); + instance->mode_ctx.write_ctx.mfc_data_target = malloc(sizeof(MfClassicData)); + + instance->mode_ctx.write_ctx.need_halt_before_write = true; + + return instance; +} + +void gen2_poller_free(Gen2Poller* instance) { + furi_assert(instance); + furi_assert(instance->data); + furi_assert(instance->crypto); + furi_assert(instance->tx_plain_buffer); + furi_assert(instance->rx_plain_buffer); + furi_assert(instance->tx_encrypted_buffer); + furi_assert(instance->rx_encrypted_buffer); + + nfc_poller_free(instance->poller); + mf_classic_free(instance->data); + crypto1_free(instance->crypto); + bit_buffer_free(instance->tx_plain_buffer); + bit_buffer_free(instance->rx_plain_buffer); + bit_buffer_free(instance->tx_encrypted_buffer); + bit_buffer_free(instance->rx_encrypted_buffer); + + free(instance->mode_ctx.write_ctx.mfc_data_source); + free(instance->mode_ctx.write_ctx.mfc_data_target); + + free(instance); +} + +NfcCommand gen2_poller_detect_callback(NfcGenericEvent event, void* context) { + furi_assert(context); + furi_assert(event.protocol == NfcProtocolIso14443_3a); + furi_assert(event.event_data); + furi_assert(event.instance); + + NfcCommand command = NfcCommandStop; + Gen2PollerDetectContext* detect_ctx = context; + Iso14443_3aPoller* iso3_poller = event.instance; + Iso14443_3aPollerEvent* iso3_event = event.event_data; + detect_ctx->error = Gen2PollerErrorTimeout; + + bit_buffer_reset(detect_ctx->tx_buffer); + bit_buffer_append_byte(detect_ctx->tx_buffer, GEN2_CMD_READ_ATS); + bit_buffer_append_byte(detect_ctx->tx_buffer, GEN2_FSDI_256 << 4); + + if(iso3_event->type == Iso14443_3aPollerEventTypeReady) { + do { + const Iso14443_3aError iso14443_3a_error = iso14443_3a_poller_send_standard_frame( + iso3_poller, detect_ctx->tx_buffer, detect_ctx->rx_buffer, GEN2_POLLER_MAX_FWT); + + if(iso14443_3a_error != Iso14443_3aErrorNone && + iso14443_3a_error != Iso14443_3aErrorWrongCrc) { + FURI_LOG_E(TAG, "ATS request failed"); + detect_ctx->error = Gen2PollerErrorProtocol; + break; + + } else { + FURI_LOG_D(TAG, "ATS request succeeded:"); + // Check against known ATS responses + for(size_t i = 0; i < COUNT_OF(GEN2_ATS); i++) { + if(memcmp( + bit_buffer_get_data(detect_ctx->rx_buffer), + GEN2_ATS[i], + sizeof(GEN2_ATS[i])) == 0) { + detect_ctx->error = Gen2PollerErrorNone; + break; + } + } + } + } while(false); + } else if(iso3_event->type == Iso14443_3aPollerEventTypeError) { + detect_ctx->error = Gen2PollerErrorTimeout; + } + furi_thread_flags_set(detect_ctx->thread_id, GEN2_POLLER_THREAD_FLAG_DETECTED); + + return command; +} + +Gen2PollerError gen2_poller_detect(Nfc* nfc) { + furi_assert(nfc); + + Gen2PollerDetectContext detect_ctx = { + .poller = nfc_poller_alloc(nfc, NfcProtocolIso14443_3a), + .tx_buffer = bit_buffer_alloc(GEN2_POLLER_MAX_BUFFER_SIZE), + .rx_buffer = bit_buffer_alloc(GEN2_POLLER_MAX_BUFFER_SIZE), + .thread_id = furi_thread_get_current_id(), + .detected = false, + .error = Gen2PollerErrorNone, + }; + + nfc_poller_start(detect_ctx.poller, gen2_poller_detect_callback, &detect_ctx); + uint32_t flags = + furi_thread_flags_wait(GEN2_POLLER_THREAD_FLAG_DETECTED, FuriFlagWaitAny, FuriWaitForever); + if(flags & GEN2_POLLER_THREAD_FLAG_DETECTED) { + furi_thread_flags_clear(GEN2_POLLER_THREAD_FLAG_DETECTED); + } + nfc_poller_stop(detect_ctx.poller); + + bit_buffer_free(detect_ctx.tx_buffer); + bit_buffer_free(detect_ctx.rx_buffer); + nfc_poller_free(detect_ctx.poller); + + return detect_ctx.error; +} + +NfcCommand gen2_poller_idle_handler(Gen2Poller* instance) { + furi_assert(instance); + + NfcCommand command = NfcCommandContinue; + + instance->mode_ctx.write_ctx.current_block = 0; + instance->gen2_event.type = Gen2PollerEventTypeDetected; + command = instance->callback(instance->gen2_event, instance->context); + instance->state = Gen2PollerStateRequestMode; + + return command; +} + +NfcCommand gen2_poller_request_mode_handler(Gen2Poller* instance) { + furi_assert(instance); + + NfcCommand command = NfcCommandContinue; + + instance->gen2_event.type = Gen2PollerEventTypeRequestMode; + command = instance->callback(instance->gen2_event, instance->context); + instance->mode = instance->gen2_event_data.poller_mode.mode; + if(instance->gen2_event_data.poller_mode.mode == Gen2PollerModeWipe) { + instance->state = Gen2PollerStateWriteTargetDataRequest; + } else { + instance->state = Gen2PollerStateWriteSourceDataRequest; + } + + return command; +} + +NfcCommand gen2_poller_write_source_data_request_handler(Gen2Poller* instance) { + NfcCommand command = NfcCommandContinue; + + instance->gen2_event.type = Gen2PollerEventTypeRequestDataToWrite; + command = instance->callback(instance->gen2_event, instance->context); + memcpy( + instance->mode_ctx.write_ctx.mfc_data_source, + instance->gen2_event_data.data_to_write.mfc_data, + sizeof(MfClassicData)); + instance->state = Gen2PollerStateWriteTargetDataRequest; + + return command; +} + +NfcCommand gen2_poller_write_target_data_request_handler(Gen2Poller* instance) { + NfcCommand command = NfcCommandContinue; + + instance->gen2_event.type = Gen2PollerEventTypeRequestTargetData; + command = instance->callback(instance->gen2_event, instance->context); + memcpy( + instance->mode_ctx.write_ctx.mfc_data_target, + instance->gen2_event_data.target_data.mfc_data, + sizeof(MfClassicData)); + if(instance->mode == Gen2PollerModeWipe) { + instance->state = Gen2PollerStateWipe; + } else { + instance->state = Gen2PollerStateWrite; + } + + return command; +} + +Gen2PollerError gen2_poller_write_block_handler( + Gen2Poller* instance, + uint8_t block_num, + const MfClassicBlock* block) { + furi_assert(instance); + + Gen2PollerError error = Gen2PollerErrorNone; + Gen2PollerWriteContext* write_ctx = &instance->mode_ctx.write_ctx; + MfClassicKey auth_key = write_ctx->auth_key; + + do { + // Compare the target and source data + if(memcmp(block->data, write_ctx->mfc_data_target->block[block_num].data, 16) == 0) { + FURI_LOG_D(TAG, "Block %d is the same, skipping", block_num); + break; + } + + // Reauth if necessary + if(write_ctx->need_halt_before_write) { + FURI_LOG_D(TAG, "Auth before writing block %d", write_ctx->current_block); + error = gen2_poller_auth( + instance, write_ctx->current_block, &auth_key, write_ctx->write_key, NULL); + if(error != Gen2PollerErrorNone) { + FURI_LOG_D( + TAG, "Failed to auth to block %d for writing", write_ctx->current_block); + break; + } + } + + // Write the block + error = gen2_poller_write_block(instance, write_ctx->current_block, block); + if(error != Gen2PollerErrorNone) { + FURI_LOG_D(TAG, "Failed to write block %d", write_ctx->current_block); + break; + } + } while(false); + FURI_LOG_D(TAG, "Block %d finished, halting", write_ctx->current_block); + gen2_poller_halt(instance); + return error; +} + +NfcCommand gen2_poller_wipe_handler(Gen2Poller* instance) { + NfcCommand command = NfcCommandContinue; + Gen2PollerError error = Gen2PollerErrorNone; + Gen2PollerWriteContext* write_ctx = &instance->mode_ctx.write_ctx; + uint8_t block_num = write_ctx->current_block; + + do { + // Check whether the ACs for that block are known in target data + if(!mf_classic_is_block_read( + write_ctx->mfc_data_target, + mf_classic_get_sector_trailer_num_by_block(block_num))) { + FURI_LOG_E(TAG, "Sector trailer for block %d not present in target data", block_num); + break; + } + + // Check whether ACs need to be reset and whether they can be reset + if(!gen2_poller_can_write_block(write_ctx->mfc_data_target, block_num)) { + if(!gen2_can_reset_access_conditions(write_ctx->mfc_data_target, block_num)) { + FURI_LOG_E(TAG, "Block %d cannot be written", block_num); + break; + } else { + FURI_LOG_D(TAG, "Resetting ACs for block %d", block_num); + // Generate a block with old keys and default ACs (0xFF, 0x07, 0x80) + MfClassicBlock block; + memset(&block, 0, sizeof(block)); + memcpy(block.data, write_ctx->mfc_data_target->block[block_num].data, 16); + memcpy(block.data + 6, "\xFF\x07\x80", 3); + + error = gen2_poller_write_block_handler(instance, block_num, &block); + if(error != Gen2PollerErrorNone) { + FURI_LOG_E(TAG, "Failed to reset ACs for block %d", block_num); + break; + } else { + FURI_LOG_D(TAG, "ACs for block %d reset", block_num); + memcpy(write_ctx->mfc_data_target->block[block_num].data, block.data, 16); + } + } + } + + // Figure out which key to use for writing + write_ctx->write_key = + gen2_poller_get_key_type_to_write(write_ctx->mfc_data_target, block_num); + + // Get the key to use for writing from the target data + MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector( + write_ctx->mfc_data_target, mf_classic_get_sector_by_block(block_num)); + if(write_ctx->write_key == MfClassicKeyTypeA) { + write_ctx->auth_key = sec_tr->key_a; + } else { + write_ctx->auth_key = sec_tr->key_b; + } + + // Write the default block depending on the block type + if(block_num == 0) { + error = + gen2_poller_write_block_handler(instance, block_num, &gen2_poller_default_block_0); + } else if(mf_classic_is_sector_trailer(block_num)) { + error = gen2_poller_write_block_handler( + instance, block_num, &gen2_poller_default_sector_trailer_block); + } else { + error = gen2_poller_write_block_handler( + instance, block_num, &gen2_poller_default_empty_block); + } + if(error != Gen2PollerErrorNone) { + FURI_LOG_E(TAG, "Couldn't write block %d", block_num); + } + } while(false); + + write_ctx->current_block++; + + if(error != Gen2PollerErrorNone) { + FURI_LOG_D(TAG, "Error occurred: %d", error); + } + + if(write_ctx->current_block == + mf_classic_get_total_block_num(write_ctx->mfc_data_target->type)) { + instance->state = Gen2PollerStateSuccess; + } + + return command; +} + +NfcCommand gen2_poller_write_handler(Gen2Poller* instance) { + NfcCommand command = NfcCommandContinue; + Gen2PollerError error = Gen2PollerErrorNone; + Gen2PollerWriteContext* write_ctx = &instance->mode_ctx.write_ctx; + uint8_t block_num = write_ctx->current_block; + + do { + // Check whether the block is present in the source data + if(!mf_classic_is_block_read(write_ctx->mfc_data_source, block_num)) { + // FURI_LOG_E(TAG, "Block %d not present in source data", block_num); + break; + } + + // Check whether the ACs for that block are known in target data + if(!mf_classic_is_block_read( + write_ctx->mfc_data_target, + mf_classic_get_sector_trailer_num_by_block(block_num))) { + FURI_LOG_E(TAG, "Sector trailer for block %d not present in target data", block_num); + break; + } + + // Check whether ACs need to be reset and whether they can be reset + if(!gen2_poller_can_write_block(write_ctx->mfc_data_target, block_num)) { + if(!gen2_can_reset_access_conditions(write_ctx->mfc_data_target, block_num)) { + FURI_LOG_E(TAG, "Block %d cannot be written", block_num); + break; + } else { + FURI_LOG_D(TAG, "Resetting ACs for block %d", block_num); + // Generate a block with old keys and default ACs (0xFF, 0x07, 0x80) + MfClassicBlock block; + memset(&block, 0, sizeof(block)); + memcpy(block.data, write_ctx->mfc_data_target->block[block_num].data, 16); + memcpy(block.data + 6, "\xFF\x07\x80", 3); + + error = gen2_poller_write_block_handler(instance, block_num, &block); + if(error != Gen2PollerErrorNone) { + FURI_LOG_E(TAG, "Failed to reset ACs for block %d", block_num); + break; + } else { + FURI_LOG_D(TAG, "ACs for block %d reset", block_num); + memcpy(write_ctx->mfc_data_target->block[block_num].data, block.data, 16); + } + } + } + + // Figure out which key to use for writing + write_ctx->write_key = + gen2_poller_get_key_type_to_write(write_ctx->mfc_data_target, block_num); + + // Get the key to use for writing from the target data + MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector( + write_ctx->mfc_data_target, mf_classic_get_sector_by_block(block_num)); + if(write_ctx->write_key == MfClassicKeyTypeA) { + write_ctx->auth_key = sec_tr->key_a; + } else { + write_ctx->auth_key = sec_tr->key_b; + } + + // Write the block + error = gen2_poller_write_block_handler( + instance, block_num, &write_ctx->mfc_data_source->block[block_num]); + if(error != Gen2PollerErrorNone) { + FURI_LOG_E(TAG, "Couldn't write block %d", block_num); + } + } while(false); + write_ctx->current_block++; + + if(error != Gen2PollerErrorNone) { + FURI_LOG_D(TAG, "Error occurred: %d", error); + } else if( + write_ctx->current_block == + mf_classic_get_total_block_num(write_ctx->mfc_data_source->type)) { + instance->state = Gen2PollerStateSuccess; + } + + return command; +} + +NfcCommand gen2_poller_success_handler(Gen2Poller* instance) { + furi_assert(instance); + + NfcCommand command = NfcCommandContinue; + + instance->gen2_event.type = Gen2PollerEventTypeSuccess; + command = instance->callback(instance->gen2_event, instance->context); + instance->state = Gen2PollerStateIdle; + + return command; +} + +NfcCommand gen2_poller_fail_handler(Gen2Poller* instance) { + furi_assert(instance); + + NfcCommand command = NfcCommandContinue; + + instance->gen2_event.type = Gen2PollerEventTypeFail; + command = instance->callback(instance->gen2_event, instance->context); + instance->state = Gen2PollerStateIdle; + + return command; +} + +static const Gen2PollerStateHandler gen2_poller_state_handlers[Gen2PollerStateNum] = { + [Gen2PollerStateIdle] = gen2_poller_idle_handler, + [Gen2PollerStateRequestMode] = gen2_poller_request_mode_handler, + [Gen2PollerStateWipe] = gen2_poller_wipe_handler, + [Gen2PollerStateWriteSourceDataRequest] = gen2_poller_write_source_data_request_handler, + [Gen2PollerStateWriteTargetDataRequest] = gen2_poller_write_target_data_request_handler, + [Gen2PollerStateWrite] = gen2_poller_write_handler, + [Gen2PollerStateSuccess] = gen2_poller_success_handler, + [Gen2PollerStateFail] = gen2_poller_fail_handler, +}; + +NfcCommand gen2_poller_callback(NfcGenericEvent event, void* context) { + furi_assert(context); + furi_assert(event.protocol == NfcProtocolIso14443_3a); + furi_assert(event.event_data); + furi_assert(event.instance); + + NfcCommand command = NfcCommandContinue; + Gen2Poller* instance = context; + instance->iso3_poller = event.instance; + Iso14443_3aPollerEvent* iso3_event = event.event_data; + + if(iso3_event->type == Iso14443_3aPollerEventTypeReady) { + command = gen2_poller_state_handlers[instance->state](instance); + } + + return command; +} + +void gen2_poller_start(Gen2Poller* instance, Gen2PollerCallback callback, void* context) { + furi_assert(instance); + furi_assert(callback); + + instance->callback = callback; + instance->context = context; + + nfc_poller_start(instance->poller, gen2_poller_callback, instance); + return; +} + +void gen2_poller_stop(Gen2Poller* instance) { + furi_assert(instance); + + FURI_LOG_D(TAG, "Stopping Gen2 poller"); + nfc_poller_stop(instance->poller); + return; +} + +Gen2PollerWriteProblems gen2_poller_check_target_problems(NfcDevice* target_dev) { + furi_assert(target_dev); + + Gen2PollerWriteProblems problems = {0}; + const MfClassicData* mfc_data = nfc_device_get_data(target_dev, NfcProtocolMfClassic); + + if(mfc_data) { + uint16_t total_block_num = mf_classic_get_total_block_num(mfc_data->type); + for(uint16_t i = 0; i < total_block_num; i++) { + if(mf_classic_is_sector_trailer(i)) { + problems.all_problems |= + gen2_poller_can_write_sector_trailer(mfc_data, i).all_problems; + } else { + problems.all_problems |= + gen2_poller_can_write_data_block(mfc_data, i).all_problems; + } + } + } else { + problems.no_data = true; + } + + return problems; +} + +Gen2PollerWriteProblems gen2_poller_check_source_problems(NfcDevice* source_dev) { + furi_assert(source_dev); + + Gen2PollerWriteProblems problems = {0}; + const MfClassicData* mfc_data = nfc_device_get_data(source_dev, NfcProtocolMfClassic); + + if(mfc_data) { + uint16_t total_block_num = mf_classic_get_total_block_num(mfc_data->type); + for(uint16_t i = 0; i < total_block_num; i++) { + if(!mf_classic_is_block_read(mfc_data, i)) { + problems.missing_source_data = true; + } + } + } + + return problems; +} diff --git a/nfc_magic/magic/protocols/gen2/gen2_poller.h b/nfc_magic/magic/protocols/gen2/gen2_poller.h new file mode 100644 index 00000000..20243d1c --- /dev/null +++ b/nfc_magic/magic/protocols/gen2/gen2_poller.h @@ -0,0 +1,97 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + Gen2PollerErrorNone, + Gen2PollerErrorNotPresent, + Gen2PollerErrorProtocol, + Gen2PollerErrorAuth, + Gen2PollerErrorTimeout, + Gen2PollerErrorAccess, +} Gen2PollerError; + +// Possible write problems, sorted by priority top to bottom +typedef union { + uint8_t all_problems; + struct { + bool uid_locked : 1; // UID may be non-rewritable. Check data after writing + bool no_data : 1; // Shouldn't happen, mfc_data missing in nfc device + bool locked_access_bits : 1; // Access bits on the target card don't allow writing in some cases + bool missing_target_keys : 1; // Keys to write some sectors are not available + bool missing_source_data : 1; // The source dump is incomplete + }; +} Gen2PollerWriteProblems; + +#define GEN2_POLLER_WRITE_PROBLEMS_LEN (5) + +extern const char* const gen2_problem_strings[]; + +typedef enum { + Gen2PollerEventTypeDetected, + Gen2PollerEventTypeRequestMode, + Gen2PollerEventTypeRequestDataToWrite, + Gen2PollerEventTypeRequestTargetData, + + Gen2PollerEventTypeSuccess, + Gen2PollerEventTypeFail, +} Gen2PollerEventType; + +typedef enum { + Gen2PollerModeWipe, + Gen2PollerModeWrite, +} Gen2PollerMode; + +typedef struct { + Gen2PollerMode mode; +} Gen2PollerEventDataRequestMode; + +typedef struct { + const MfClassicData* mfc_data; +} Gen2PollerEventDataRequestDataToWrite; + +typedef struct { + const MfClassicData* mfc_data; +} Gen2PollerEventDataRequestTargetData; + +typedef union { + Gen2PollerEventDataRequestMode poller_mode; + Gen2PollerEventDataRequestDataToWrite data_to_write; + Gen2PollerEventDataRequestTargetData target_data; +} Gen2PollerEventData; + +typedef struct { + Gen2PollerEventType type; + Gen2PollerEventData* data; +} Gen2PollerEvent; + +typedef NfcCommand (*Gen2PollerCallback)(Gen2PollerEvent event, void* context); + +typedef struct Gen2Poller Gen2Poller; + +Gen2PollerError gen2_poller_detect(Nfc* nfc); + +Gen2Poller* gen2_poller_alloc(Nfc* nfc); + +void gen2_poller_free(Gen2Poller* instance); + +void gen2_poller_start(Gen2Poller* instance, Gen2PollerCallback callback, void* context); + +void gen2_poller_stop(Gen2Poller* instance); + +Gen2PollerWriteProblems gen2_poller_check_target_problems(NfcDevice* target_dev); + +Gen2PollerWriteProblems gen2_poller_check_source_problems(NfcDevice* source_dev); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/nfc_magic/magic/protocols/gen2/gen2_poller_i.c b/nfc_magic/magic/protocols/gen2/gen2_poller_i.c new file mode 100644 index 00000000..7e403f57 --- /dev/null +++ b/nfc_magic/magic/protocols/gen2/gen2_poller_i.c @@ -0,0 +1,629 @@ +#include "gen2_poller_i.h" +#include + +#include +#include "furi_hal_random.h" + +#include + +#define TAG "GEN2_I" + +MfClassicError mf_classic_process_error(Iso14443_3aError error) { + MfClassicError ret = MfClassicErrorNone; + + switch(error) { + case Iso14443_3aErrorNone: + ret = MfClassicErrorNone; + break; + case Iso14443_3aErrorNotPresent: + ret = MfClassicErrorNotPresent; + break; + case Iso14443_3aErrorColResFailed: + case Iso14443_3aErrorCommunication: + case Iso14443_3aErrorWrongCrc: + ret = MfClassicErrorProtocol; + break; + case Iso14443_3aErrorTimeout: + ret = MfClassicErrorTimeout; + break; + default: + ret = MfClassicErrorProtocol; + break; + } + return ret; +} + +Gen2PollerError gen2_poller_process_iso3_error(Iso14443_3aError error) { + Gen2PollerError ret = Gen2PollerErrorNone; + + switch(error) { + case Iso14443_3aErrorNone: + ret = Gen2PollerErrorNone; + break; + case Iso14443_3aErrorNotPresent: + ret = Gen2PollerErrorNotPresent; + break; + case Iso14443_3aErrorWrongCrc: + ret = Gen2PollerErrorProtocol; + break; + case Iso14443_3aErrorTimeout: + ret = Gen2PollerErrorTimeout; + break; + default: + ret = Gen2PollerErrorProtocol; + break; + } + return ret; +} + +Gen2PollerError gen2_poller_process_mifare_classic_error(MfClassicError error) { + Gen2PollerError ret = Gen2PollerErrorNone; + + switch(error) { + case MfClassicErrorNone: + ret = Gen2PollerErrorNone; + break; + case MfClassicErrorNotPresent: + ret = Gen2PollerErrorNotPresent; + break; + case MfClassicErrorProtocol: + ret = Gen2PollerErrorProtocol; + break; + case MfClassicErrorAuth: + ret = Gen2PollerErrorAuth; + break; + case MfClassicErrorTimeout: + ret = Gen2PollerErrorTimeout; + break; + default: + ret = Gen2PollerErrorProtocol; + break; + } + + return ret; +} + +static Gen2PollerError gen2_poller_get_nt_common( + Gen2Poller* instance, + uint8_t block_num, + MfClassicKeyType key_type, + MfClassicNt* nt, + bool is_nested) { + MfClassicError ret = MfClassicErrorNone; + Iso14443_3aError error = Iso14443_3aErrorNone; + + do { + uint8_t auth_type = (key_type == MfClassicKeyTypeB) ? MF_CLASSIC_CMD_AUTH_KEY_B : + MF_CLASSIC_CMD_AUTH_KEY_A; + uint8_t auth_cmd[2] = {auth_type, block_num}; + bit_buffer_copy_bytes(instance->tx_plain_buffer, auth_cmd, sizeof(auth_cmd)); + + if(is_nested) { + iso14443_crc_append(Iso14443CrcTypeA, instance->tx_plain_buffer); + crypto1_encrypt( + instance->crypto, NULL, instance->tx_plain_buffer, instance->tx_encrypted_buffer); + error = iso14443_3a_poller_txrx_custom_parity( + instance->iso3_poller, + instance->tx_encrypted_buffer, + instance->rx_plain_buffer, // NT gets decrypted by mf_classic_async_auth + GEN2_POLLER_MAX_FWT); + if(error != Iso14443_3aErrorNone) { + ret = mf_classic_process_error(error); + break; + } + } else { + FURI_LOG_D(TAG, "Plain auth cmd"); + error = iso14443_3a_poller_send_standard_frame( + instance->iso3_poller, + instance->tx_plain_buffer, + instance->rx_plain_buffer, + GEN2_POLLER_MAX_FWT); + if(error != Iso14443_3aErrorWrongCrc) { + ret = mf_classic_process_error(error); + break; + } + } + if(bit_buffer_get_size_bytes(instance->rx_plain_buffer) != sizeof(MfClassicNt)) { + ret = MfClassicErrorProtocol; + break; + } + + if(nt) { + bit_buffer_write_bytes(instance->rx_plain_buffer, nt->data, sizeof(MfClassicNt)); + } + } while(false); + + return gen2_poller_process_mifare_classic_error(ret); +} + +Gen2PollerError gen2_poller_get_nt( + Gen2Poller* instance, + uint8_t block_num, + MfClassicKeyType key_type, + MfClassicNt* nt) { + return gen2_poller_get_nt_common(instance, block_num, key_type, nt, false); +} + +Gen2PollerError gen2_poller_get_nt_nested( + Gen2Poller* instance, + uint8_t block_num, + MfClassicKeyType key_type, + MfClassicNt* nt) { + return gen2_poller_get_nt_common(instance, block_num, key_type, nt, true); +} + +static Gen2PollerError gen2_poller_auth_common( + Gen2Poller* instance, + uint8_t block_num, + MfClassicKey* key, + MfClassicKeyType key_type, + MfClassicAuthContext* data, + bool is_nested) { + Gen2PollerError ret = Gen2PollerErrorNone; + Iso14443_3aError error = Iso14443_3aErrorNone; + + do { + iso14443_3a_copy(instance->data->iso14443_3a_data, nfc_poller_get_data(instance->poller)); + + MfClassicNt nt = {}; + if(is_nested) { + ret = gen2_poller_get_nt_nested(instance, block_num, key_type, &nt); + } else { + ret = gen2_poller_get_nt(instance, block_num, key_type, &nt); + } + if(ret != Gen2PollerErrorNone) break; + if(data) { + data->nt = nt; + } + + uint32_t cuid = iso14443_3a_get_cuid(instance->data->iso14443_3a_data); + uint64_t key_num = bit_lib_bytes_to_num_be(key->data, sizeof(MfClassicKey)); + MfClassicNr nr = {}; + furi_hal_random_fill_buf(nr.data, sizeof(MfClassicNr)); + + crypto1_encrypt_reader_nonce( + instance->crypto, + key_num, + cuid, + nt.data, + nr.data, + instance->tx_encrypted_buffer, + is_nested); + error = iso14443_3a_poller_txrx_custom_parity( + instance->iso3_poller, + instance->tx_encrypted_buffer, + instance->rx_encrypted_buffer, + GEN2_POLLER_MAX_FWT); + + if(error != Iso14443_3aErrorNone) { + ret = gen2_poller_process_iso3_error(error); + break; + } + if(bit_buffer_get_size_bytes(instance->rx_encrypted_buffer) != 4) { + ret = Gen2PollerErrorAuth; + } + + crypto1_word(instance->crypto, 0, 0); + instance->auth_state = Gen2AuthStatePassed; + + if(data) { + data->nr = nr; + const uint8_t* nr_ar = bit_buffer_get_data(instance->tx_encrypted_buffer); + memcpy(data->ar.data, &nr_ar[4], sizeof(MfClassicAr)); + bit_buffer_write_bytes( + instance->rx_encrypted_buffer, data->at.data, sizeof(MfClassicAt)); + } + } while(false); + + if(ret != Gen2PollerErrorNone) { + iso14443_3a_poller_halt(instance->iso3_poller); + } + + return ret; +} + +Gen2PollerError gen2_poller_auth( + Gen2Poller* instance, + uint8_t block_num, + MfClassicKey* key, + MfClassicKeyType key_type, + MfClassicAuthContext* data) { + return gen2_poller_auth_common(instance, block_num, key, key_type, data, false); +} + +Gen2PollerError gen2_poller_halt(Gen2Poller* instance) { + Gen2PollerError ret = Gen2PollerErrorNone; + Iso14443_3aError error = Iso14443_3aErrorNone; + + do { + uint8_t halt_cmd[2] = {MF_CLASSIC_CMD_HALT_MSB, MF_CLASSIC_CMD_HALT_LSB}; + bit_buffer_copy_bytes(instance->tx_plain_buffer, halt_cmd, sizeof(halt_cmd)); + iso14443_crc_append(Iso14443CrcTypeA, instance->tx_plain_buffer); + + if(instance->auth_state == Gen2AuthStatePassed) { + // Send an encrypted halt command + crypto1_encrypt( + instance->crypto, NULL, instance->tx_plain_buffer, instance->tx_encrypted_buffer); + FURI_LOG_D(TAG, "Send enc halt"); + error = iso14443_3a_poller_txrx_custom_parity( + instance->iso3_poller, + instance->tx_encrypted_buffer, + instance->rx_encrypted_buffer, + GEN2_POLLER_MAX_FWT); + } + + if(error != Iso14443_3aErrorNone) { + FURI_LOG_D(TAG, "Enc halt error"); + // Do not break because we still need to halt the iso3 poller + } + + // Send a regular halt command to halt the iso3 poller + FURI_LOG_D(TAG, "Send reg halt"); + error = iso14443_3a_poller_halt(instance->iso3_poller); + + if(error != Iso14443_3aErrorTimeout) { + FURI_LOG_D(TAG, "Reg halt error"); + // Do not break as well becaue the first halt command might have worked + // and the card didn't respond because it was already halted + } + + crypto1_reset(instance->crypto); + instance->auth_state = Gen2AuthStateIdle; + } while(false); + + return ret; +} + +Gen2PollerError + gen2_poller_write_block(Gen2Poller* instance, uint8_t block_num, const MfClassicBlock* data) { + Gen2PollerError ret = Gen2PollerErrorNone; + Iso14443_3aError error = Iso14443_3aErrorNone; + + do { + uint8_t write_block_cmd[2] = {MF_CLASSIC_CMD_WRITE_BLOCK, block_num}; + bit_buffer_copy_bytes(instance->tx_plain_buffer, write_block_cmd, sizeof(write_block_cmd)); + iso14443_crc_append(Iso14443CrcTypeA, instance->tx_plain_buffer); + + crypto1_encrypt( + instance->crypto, NULL, instance->tx_plain_buffer, instance->tx_encrypted_buffer); + + error = iso14443_3a_poller_txrx_custom_parity( + instance->iso3_poller, + instance->tx_encrypted_buffer, + instance->rx_encrypted_buffer, + GEN2_POLLER_MAX_FWT); + if(error != Iso14443_3aErrorNone) { + ret = gen2_poller_process_iso3_error(error); + break; + } + if(bit_buffer_get_size(instance->rx_encrypted_buffer) != 4) { + ret = Gen2PollerErrorProtocol; + break; + } + + crypto1_decrypt( + instance->crypto, instance->rx_encrypted_buffer, instance->rx_plain_buffer); + + if(bit_buffer_get_byte(instance->rx_plain_buffer, 0) != MF_CLASSIC_CMD_ACK) { + FURI_LOG_D(TAG, "NACK received"); + ret = Gen2PollerErrorProtocol; + break; + } + + bit_buffer_copy_bytes(instance->tx_plain_buffer, data->data, sizeof(MfClassicBlock)); + iso14443_crc_append(Iso14443CrcTypeA, instance->tx_plain_buffer); + + crypto1_encrypt( + instance->crypto, NULL, instance->tx_plain_buffer, instance->tx_encrypted_buffer); + + error = iso14443_3a_poller_txrx_custom_parity( + instance->iso3_poller, + instance->tx_encrypted_buffer, + instance->rx_encrypted_buffer, + GEN2_POLLER_MAX_FWT); + if(error != Iso14443_3aErrorNone) { + ret = gen2_poller_process_iso3_error(error); + break; + } + if(bit_buffer_get_size(instance->rx_encrypted_buffer) != 4) { + ret = Gen2PollerErrorProtocol; + break; + } + + crypto1_decrypt( + instance->crypto, instance->rx_encrypted_buffer, instance->rx_plain_buffer); + + if(bit_buffer_get_byte(instance->rx_plain_buffer, 0) != MF_CLASSIC_CMD_ACK) { + FURI_LOG_D(TAG, "NACK received"); + ret = Gen2PollerErrorProtocol; + break; + } + } while(false); + + return ret; +} + +bool gen2_poller_can_write_block(const MfClassicData* target_data, uint8_t block_num) { + furi_assert(target_data); + + bool can_write = true; + + if(block_num == 0 && target_data->iso14443_3a_data->uid_len == 7) { + // 7-byte UID gen2 cards are not supported yet, need further testing + can_write = false; + } + + if(mf_classic_is_sector_trailer(block_num)) { + can_write = gen2_poller_can_write_sector_trailer(target_data, block_num).all_problems == 0; + } else { + can_write = gen2_poller_can_write_data_block(target_data, block_num).all_problems == 0; + } + + return can_write; +} + +Gen2PollerWriteProblems + gen2_poller_can_write_data_block(const MfClassicData* target_data, uint8_t block_num) { + // Check whether it's possible to write the block + furi_assert(target_data); + + // Check rules: + // 1. Check if block is read + // 2. Check if we have any of the keys + // 3. For each key, check if we can write the block + // 3.1. If none of the keys can write the block, check whether access conditions can be reset to allow writing + // 3.2 If the above conditions are not met, return an error code + + Gen2PollerWriteProblems can_write = {0}; + + bool has_key_a = mf_classic_is_key_found( + target_data, mf_classic_get_sector_by_block(block_num), MfClassicKeyTypeA); + bool has_key_b = mf_classic_is_key_found( + target_data, mf_classic_get_sector_by_block(block_num), MfClassicKeyTypeB); + + if(!has_key_a && !has_key_b) { + can_write.missing_target_keys = true; + } + if(!gen2_is_allowed_access( + target_data, block_num, MfClassicKeyTypeA, MfClassicActionDataWrite) && + !gen2_is_allowed_access( + target_data, block_num, MfClassicKeyTypeB, MfClassicActionDataWrite)) { + if(!gen2_can_reset_access_conditions(target_data, block_num)) { + can_write.locked_access_bits = true; + } + } + + return can_write; +} + +Gen2PollerWriteProblems + gen2_poller_can_write_sector_trailer(const MfClassicData* target_data, uint8_t block_num) { + // Check whether it's possible to write the sector trailer + furi_assert(target_data); + + // Check rules: + // 1. Check if block is read + // 2. Check if we have any of the keys + // 3. For each key, check if we can write the block + // 3.1 Check that at least one of the keys can write Key A + // 3.1.1 If none of the keys can write Key A, check whether access conditions can be reset to allow writing + // 3.2 Check that at least one of the keys can write the Access Conditions + // 3.3 Check that at least one of the keys can write Key B + // 3.3.1 If none of the keys can write Key B, check whether access conditions can be reset to allow writing + // 3.4 If any of the above conditions are not met, return an error code + + Gen2PollerWriteProblems can_write = {0}; + + bool has_key_a = mf_classic_is_key_found( + target_data, mf_classic_get_sector_by_block(block_num), MfClassicKeyTypeA); + bool has_key_b = mf_classic_is_key_found( + target_data, mf_classic_get_sector_by_block(block_num), MfClassicKeyTypeB); + + if(!has_key_a && !has_key_b) { + can_write.missing_target_keys = true; + } + if(!gen2_is_allowed_access( + target_data, block_num, MfClassicKeyTypeA, MfClassicActionKeyAWrite) && + !gen2_is_allowed_access( + target_data, block_num, MfClassicKeyTypeB, MfClassicActionKeyAWrite)) { + if(!gen2_can_reset_access_conditions(target_data, block_num)) { + can_write.locked_access_bits = true; + } + } + if(!gen2_is_allowed_access(target_data, block_num, MfClassicKeyTypeA, MfClassicActionACWrite) && + !gen2_is_allowed_access(target_data, block_num, MfClassicKeyTypeB, MfClassicActionACWrite)) { + can_write.locked_access_bits = true; + } + if(!gen2_is_allowed_access( + target_data, block_num, MfClassicKeyTypeA, MfClassicActionKeyBWrite) && + !gen2_is_allowed_access( + target_data, block_num, MfClassicKeyTypeB, MfClassicActionKeyBWrite)) { + if(!gen2_can_reset_access_conditions(target_data, block_num)) { + can_write.locked_access_bits = true; + } + } + + return can_write; +} + +bool gen2_can_reset_access_conditions(const MfClassicData* target_data, uint8_t block_num) { + // Check whether it's possible to reset the access conditions + furi_assert(target_data); + + // Check rules: + // 1. Check if the sector trailer for this block is read + // 2. Check if we have any of the keys + // 3. For each key, check if we can write the access conditions + // 3.1. If none of the keys can write the access conditions, return false + + bool can_reset = false; + + bool has_key_a = mf_classic_is_key_found( + target_data, mf_classic_get_sector_by_block(block_num), MfClassicKeyTypeA); + bool has_key_b = mf_classic_is_key_found( + target_data, mf_classic_get_sector_by_block(block_num), MfClassicKeyTypeB); + uint8_t sector_tr_num = mf_classic_get_sector_trailer_num_by_block(block_num); + + if(!mf_classic_is_block_read(target_data, sector_tr_num)) { + can_reset = false; + return can_reset; + } + + if(!has_key_a && !has_key_b) { + can_reset = false; + return can_reset; + } + if(gen2_is_allowed_access(target_data, block_num, MfClassicKeyTypeA, MfClassicActionACWrite) || + gen2_is_allowed_access(target_data, block_num, MfClassicKeyTypeB, MfClassicActionACWrite)) { + can_reset = true; + } + + return can_reset; +} + +MfClassicKeyType + gen2_poller_get_key_type_to_write(const MfClassicData* target_data, uint8_t block_num) { + // Get the key type to use for writing + // We assume that at least one of the keys can write the block + furi_assert(target_data); + + MfClassicKeyType key_type = MfClassicKeyTypeA; + + if(gen2_is_allowed_access( + target_data, block_num, MfClassicKeyTypeA, MfClassicActionDataWrite)) { + key_type = MfClassicKeyTypeA; + } else if(gen2_is_allowed_access( + target_data, block_num, MfClassicKeyTypeB, MfClassicActionDataWrite)) { + key_type = MfClassicKeyTypeB; + } + + return key_type; +} + +static bool gen2_is_allowed_access_sector_trailer( + const MfClassicData* data, + uint8_t block_num, + MfClassicKeyType key_type, + MfClassicAction action) { + uint8_t sector_num = mf_classic_get_sector_by_block(block_num); + MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, sector_num); + uint8_t* access_bits_arr = sec_tr->access_bits.data; + uint8_t AC = ((access_bits_arr[1] >> 5) & 0x04) | ((access_bits_arr[2] >> 2) & 0x02) | + ((access_bits_arr[2] >> 7) & 0x01); + FURI_LOG_T("NFC", "AC: %02X", AC); + + switch(action) { + case MfClassicActionKeyARead: { + return false; + } + case MfClassicActionKeyAWrite: + case MfClassicActionKeyBWrite: { + return ( + (key_type == MfClassicKeyTypeA && (AC == 0x00 || AC == 0x01)) || + (key_type == MfClassicKeyTypeB && + (AC == 0x00 || AC == 0x04 || AC == 0x03 || AC == 0x01))); + } + case MfClassicActionKeyBRead: { + return (key_type == MfClassicKeyTypeA && (AC == 0x00 || AC == 0x02 || AC == 0x01)) || + (key_type == MfClassicKeyTypeB && (AC == 0x00 || AC == 0x02 || AC == 0x01)); + } + case MfClassicActionACRead: { + return ((key_type == MfClassicKeyTypeA) || (key_type == MfClassicKeyTypeB)); + } + case MfClassicActionACWrite: { + return ( + (key_type == MfClassicKeyTypeA && (AC == 0x01)) || + (key_type == MfClassicKeyTypeB && (AC == 0x01 || AC == 0x03 || AC == 0x05))); + } + default: + return false; + } + return true; +} + +bool gen2_is_allowed_access_data_block( + MfClassicSectorTrailer* sec_tr, + uint8_t block_num, + MfClassicKeyType key_type, + MfClassicAction action) { + // Same as mf_classic_is_allowed_access_data_block but with sector 0 allowed + furi_assert(sec_tr); + + uint8_t* access_bits_arr = sec_tr->access_bits.data; + + uint8_t sector_block = 0; + if(block_num <= 128) { + sector_block = block_num & 0x03; + } else { + sector_block = (block_num & 0x0f) / 5; + } + + uint8_t AC; + switch(sector_block) { + case 0x00: { + AC = ((access_bits_arr[1] >> 2) & 0x04) | ((access_bits_arr[2] << 1) & 0x02) | + ((access_bits_arr[2] >> 4) & 0x01); + break; + } + case 0x01: { + AC = ((access_bits_arr[1] >> 3) & 0x04) | ((access_bits_arr[2] >> 0) & 0x02) | + ((access_bits_arr[2] >> 5) & 0x01); + break; + } + case 0x02: { + AC = ((access_bits_arr[1] >> 4) & 0x04) | ((access_bits_arr[2] >> 1) & 0x02) | + ((access_bits_arr[2] >> 6) & 0x01); + break; + } + default: + return false; + } + + switch(action) { + case MfClassicActionDataRead: { + return ( + (key_type == MfClassicKeyTypeA && !(AC == 0x03 || AC == 0x05 || AC == 0x07)) || + (key_type == MfClassicKeyTypeB && !(AC == 0x07))); + } + case MfClassicActionDataWrite: { + return ( + (key_type == MfClassicKeyTypeA && (AC == 0x00)) || + (key_type == MfClassicKeyTypeB && + (AC == 0x00 || AC == 0x04 || AC == 0x06 || AC == 0x03))); + } + case MfClassicActionDataInc: { + return ( + (key_type == MfClassicKeyTypeA && (AC == 0x00)) || + (key_type == MfClassicKeyTypeB && (AC == 0x00 || AC == 0x06))); + } + case MfClassicActionDataDec: { + return ( + (key_type == MfClassicKeyTypeA && (AC == 0x00 || AC == 0x06 || AC == 0x01)) || + (key_type == MfClassicKeyTypeB && (AC == 0x00 || AC == 0x06 || AC == 0x01))); + } + default: + return false; + } + + return false; +} + +bool gen2_is_allowed_access( + const MfClassicData* data, + uint8_t block_num, + MfClassicKeyType key_type, + MfClassicAction action) { + // Same as mf_classic_is_allowed_access but with sector 0 allowed + furi_assert(data); + + bool access_allowed = false; + if(mf_classic_is_sector_trailer(block_num)) { + access_allowed = gen2_is_allowed_access_sector_trailer(data, block_num, key_type, action); + } else { + uint8_t sector_num = mf_classic_get_sector_by_block(block_num); + MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, sector_num); + access_allowed = gen2_is_allowed_access_data_block(sec_tr, block_num, key_type, action); + } + + return access_allowed; +} \ No newline at end of file diff --git a/nfc_magic/magic/protocols/gen2/gen2_poller_i.h b/nfc_magic/magic/protocols/gen2/gen2_poller_i.h new file mode 100644 index 00000000..f30f665f --- /dev/null +++ b/nfc_magic/magic/protocols/gen2/gen2_poller_i.h @@ -0,0 +1,139 @@ +#pragma once + +#include "gen2_poller.h" +#include +#include "crypto1.h" // TODO: Move to a better home +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define GEN2_CMD_READ_ATS (0xE0) +#define GEN2_FSDI_256 (0x8U) + +#define GEN2_POLLER_BLOCK_SIZE (16) + +#define GEN2_POLLER_MAX_BUFFER_SIZE (64U) +#define GEN2_POLLER_MAX_FWT (150000U) + +typedef enum { + Gen2PollerStateIdle, + Gen2PollerStateRequestMode, + Gen2PollerStateWipe, + Gen2PollerStateWriteSourceDataRequest, + Gen2PollerStateWriteTargetDataRequest, + Gen2PollerStateWrite, + Gen2PollerStateSuccess, + Gen2PollerStateFail, + + Gen2PollerStateNum, +} Gen2PollerState; + +typedef enum { + Gen2AuthStateIdle, + Gen2AuthStatePassed, +} Gen2AuthState; + +typedef enum { + Gen2CardStateDetected, + Gen2CardStateLost, +} Gen2CardState; + +typedef struct { + MfClassicData* mfc_data_source; + MfClassicData* mfc_data_target; + MfClassicKey auth_key; + MfClassicKeyType read_key; + MfClassicKeyType write_key; + uint16_t current_block; + bool need_halt_before_write; +} Gen2PollerWriteContext; + +typedef union { + Gen2PollerWriteContext write_ctx; +} Gen2PollerModeContext; + +struct Gen2Poller { + Nfc* nfc; + Gen2PollerState state; + + NfcPoller* poller; + Iso14443_3aPoller* iso3_poller; + + Gen2AuthState auth_state; + Gen2CardState card_state; + + Gen2PollerModeContext mode_ctx; + Gen2PollerMode mode; + + Crypto1* crypto; + BitBuffer* tx_plain_buffer; + BitBuffer* tx_encrypted_buffer; + BitBuffer* rx_plain_buffer; + BitBuffer* rx_encrypted_buffer; + MfClassicData* data; + + Gen2PollerEvent gen2_event; + Gen2PollerEventData gen2_event_data; + + Gen2PollerCallback callback; + void* context; +}; + +typedef struct { + uint8_t block; + MfClassicKeyType key_type; + MfClassicNt nt; +} Gen2CollectNtContext; + +typedef struct { + uint8_t block_num; + MfClassicKey key; + MfClassicKeyType key_type; + MfClassicBlock block; +} Gen2ReadBlockContext; + +typedef struct { + uint8_t block_num; + MfClassicKey key; + MfClassicKeyType key_type; + MfClassicBlock block; +} Gen2WriteBlockContext; + +Gen2PollerError gen2_poller_write(Gen2Poller* instance); + +Gen2PollerError gen2_poller_auth( + Gen2Poller* instance, + uint8_t block_num, + MfClassicKey* key, + MfClassicKeyType key_type, + MfClassicAuthContext* data); + +Gen2PollerError gen2_poller_halt(Gen2Poller* instance); + +Gen2PollerError + gen2_poller_write_block(Gen2Poller* instance, uint8_t block_num, const MfClassicBlock* data); + +MfClassicKeyType + gen2_poller_get_key_type_to_write(const MfClassicData* mfc_data, uint8_t block_num); + +bool gen2_poller_can_write_block(const MfClassicData* mfc_data, uint8_t block_num); + +bool gen2_can_reset_access_conditions(const MfClassicData* mfc_data, uint8_t block_num); + +Gen2PollerWriteProblems + gen2_poller_can_write_data_block(const MfClassicData* mfc_data, uint8_t block_num); + +Gen2PollerWriteProblems + gen2_poller_can_write_sector_trailer(const MfClassicData* mfc_data, uint8_t block_num); + +bool gen2_is_allowed_access( + const MfClassicData* data, + uint8_t block_num, + MfClassicKeyType key_type, + MfClassicAction action); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/nfc_magic/lib/magic/protocols/gen4/gen4_poller.c b/nfc_magic/magic/protocols/gen4/gen4_poller.c similarity index 99% rename from nfc_magic/lib/magic/protocols/gen4/gen4_poller.c rename to nfc_magic/magic/protocols/gen4/gen4_poller.c index 73bf019b..9a6e8b99 100644 --- a/nfc_magic/lib/magic/protocols/gen4/gen4_poller.c +++ b/nfc_magic/magic/protocols/gen4/gen4_poller.c @@ -3,6 +3,7 @@ #include #include #include +#include #include #include diff --git a/nfc_magic/lib/magic/protocols/gen4/gen4_poller.h b/nfc_magic/magic/protocols/gen4/gen4_poller.h similarity index 100% rename from nfc_magic/lib/magic/protocols/gen4/gen4_poller.h rename to nfc_magic/magic/protocols/gen4/gen4_poller.h diff --git a/nfc_magic/lib/magic/protocols/gen4/gen4_poller_i.c b/nfc_magic/magic/protocols/gen4/gen4_poller_i.c similarity index 100% rename from nfc_magic/lib/magic/protocols/gen4/gen4_poller_i.c rename to nfc_magic/magic/protocols/gen4/gen4_poller_i.c diff --git a/nfc_magic/lib/magic/protocols/gen4/gen4_poller_i.h b/nfc_magic/magic/protocols/gen4/gen4_poller_i.h similarity index 100% rename from nfc_magic/lib/magic/protocols/gen4/gen4_poller_i.h rename to nfc_magic/magic/protocols/gen4/gen4_poller_i.h diff --git a/nfc_magic/lib/magic/protocols/nfc_magic_protocols.c b/nfc_magic/magic/protocols/nfc_magic_protocols.c similarity index 64% rename from nfc_magic/lib/magic/protocols/nfc_magic_protocols.c rename to nfc_magic/magic/protocols/nfc_magic_protocols.c index 46c3d0b6..41c27269 100644 --- a/nfc_magic/lib/magic/protocols/nfc_magic_protocols.c +++ b/nfc_magic/magic/protocols/nfc_magic_protocols.c @@ -3,8 +3,10 @@ #include static const char* nfc_magic_protocol_names[NfcMagicProtocolNum] = { - [NfcMagicProtocolGen1] = "Classic Gen 1A/B", - [NfcMagicProtocolGen4] = "Gen 4 GTU", + [NfcMagicProtocolGen1] = "Gen1A/B", + [NfcMagicProtocolGen2] = "Gen2", + [NfcMagicProtocolClassic] = "MIFARE Classic", + [NfcMagicProtocolGen4] = "Gen4 GTU", }; const char* nfc_magic_protocols_get_name(NfcMagicProtocol protocol) { diff --git a/nfc_magic/lib/magic/protocols/nfc_magic_protocols.h b/nfc_magic/magic/protocols/nfc_magic_protocols.h similarity index 75% rename from nfc_magic/lib/magic/protocols/nfc_magic_protocols.h rename to nfc_magic/magic/protocols/nfc_magic_protocols.h index 6bf87ec4..083b6fbb 100644 --- a/nfc_magic/lib/magic/protocols/nfc_magic_protocols.h +++ b/nfc_magic/magic/protocols/nfc_magic_protocols.h @@ -6,7 +6,9 @@ extern "C" { typedef enum { NfcMagicProtocolGen1, + NfcMagicProtocolGen2, NfcMagicProtocolGen4, + NfcMagicProtocolClassic, // Last to give priority to the others NfcMagicProtocolNum, NfcMagicProtocolInvalid, diff --git a/nfc_magic/nfc_magic_app.c b/nfc_magic/nfc_magic_app.c index febf5a98..bf35f5fe 100644 --- a/nfc_magic/nfc_magic_app.c +++ b/nfc_magic/nfc_magic_app.c @@ -48,13 +48,16 @@ NfcMagicApp* nfc_magic_app_alloc() { view_dispatcher_set_tick_event_callback( instance->view_dispatcher, nfc_magic_app_tick_event_callback, 100); - // Nfc device + // NFC source device (file) instance->source_dev = nfc_device_alloc(); nfc_device_set_loading_callback( instance->source_dev, nfc_magic_app_show_loading_popup, instance); instance->file_path = furi_string_alloc_set(NFC_APP_FOLDER); instance->file_name = furi_string_alloc(); + // NFC target device (tag) + instance->target_dev = nfc_device_alloc(); + // Open GUI record instance->gui = furi_record_open(RECORD_GUI); view_dispatcher_attach_to_gui( @@ -103,6 +106,20 @@ NfcMagicApp* nfc_magic_app_alloc() { view_dispatcher_add_view( instance->view_dispatcher, NfcMagicAppViewWidget, widget_get_view(instance->widget)); + // Dict attack + instance->dict_attack = dict_attack_alloc(); + view_dispatcher_add_view( + instance->view_dispatcher, + NfcMagicAppViewDictAttack, + dict_attack_get_view(instance->dict_attack)); + + // Write problems + instance->write_problems = write_problems_alloc(); + view_dispatcher_add_view( + instance->view_dispatcher, + NfcMagicAppViewWriteProblems, + write_problems_get_view(instance->write_problems)); + instance->nfc = nfc_alloc(); instance->scanner = nfc_magic_scanner_alloc(instance->nfc); @@ -112,11 +129,14 @@ NfcMagicApp* nfc_magic_app_alloc() { void nfc_magic_app_free(NfcMagicApp* instance) { furi_assert(instance); - // Nfc device + // Nfc source device nfc_device_free(instance->source_dev); furi_string_free(instance->file_name); furi_string_free(instance->file_path); + // Nfc target device + nfc_device_free(instance->target_dev); + // Submenu view_dispatcher_remove_view(instance->view_dispatcher, NfcMagicAppViewMenu); submenu_free(instance->submenu); @@ -141,6 +161,14 @@ void nfc_magic_app_free(NfcMagicApp* instance) { view_dispatcher_remove_view(instance->view_dispatcher, NfcMagicAppViewWidget); widget_free(instance->widget); + // Dict attack + view_dispatcher_remove_view(instance->view_dispatcher, NfcMagicAppViewDictAttack); + dict_attack_free(instance->dict_attack); + + // Write problems + view_dispatcher_remove_view(instance->view_dispatcher, NfcMagicAppViewWriteProblems); + write_problems_free(instance->write_problems); + // View Dispatcher view_dispatcher_free(instance->view_dispatcher); diff --git a/nfc_magic/nfc_magic_app_i.h b/nfc_magic/nfc_magic_app_i.h index 85d7ac4f..d67bc428 100644 --- a/nfc_magic/nfc_magic_app_i.h +++ b/nfc_magic/nfc_magic_app_i.h @@ -15,6 +15,8 @@ #include #include #include +#include +#include #include @@ -28,16 +30,24 @@ #include #include +#include +#include -#include "lib/magic/nfc_magic_scanner.h" -#include "lib/magic/protocols/nfc_magic_protocols.h" -#include "lib/magic/protocols/gen1a/gen1a_poller.h" -#include "lib/magic/protocols/gen4/gen4_poller.h" +#include "magic/nfc_magic_scanner.h" +#include "magic/protocols/nfc_magic_protocols.h" +#include "magic/protocols/gen1a/gen1a_poller.h" +#include "magic/protocols/gen2/gen2_poller.h" +#include "magic/protocols/gen4/gen4_poller.h" + +#include "lib/nfc/protocols/mf_classic/mf_classic_poller.h" #define NFC_APP_FOLDER ANY_PATH("nfc") #define NFC_APP_EXTENSION ".nfc" #define NFC_APP_SHADOW_EXTENSION ".shd" +#define NFC_APP_MF_CLASSIC_DICT_USER_PATH (NFC_APP_FOLDER "/assets/mf_classic_dict_user.nfc") +#define NFC_APP_MF_CLASSIC_DICT_SYSTEM_PATH (NFC_APP_FOLDER "/assets/mf_classic_dict.nfc") + #define NFC_MAGIC_APP_BYTE_INPUT_STORE_SIZE (4) enum NfcMagicAppCustomEvent { @@ -48,8 +58,33 @@ enum NfcMagicAppCustomEvent { NfcMagicAppCustomEventWorkerExit, NfcMagicAppCustomEventByteInputDone, NfcMagicAppCustomEventTextInputDone, + NfcMagicAppCustomEventCardDetected, + NfcMagicAppCustomEventCardLost, + NfcMagicAppCustomEventDictAttackDataUpdate, + NfcMagicAppCustomEventDictAttackComplete, + NfcMagicAppCustomEventDictAttackSkip, }; +typedef struct { + KeysDict* dict; + uint8_t sectors_total; + uint8_t sectors_read; + uint8_t current_sector; + uint8_t keys_found; + size_t dict_keys_total; + size_t dict_keys_current; + bool is_key_attack; + uint8_t key_attack_current_sector; + bool is_card_present; +} NfcMagicAppMfClassicDictAttackContext; + +typedef struct { + uint8_t problem_index; + uint8_t problem_index_abs; + uint8_t problems_total; + Gen2PollerWriteProblems problems; +} NfcMagicAppWriteProblemsContext; + struct NfcMagicApp { ViewDispatcher* view_dispatcher; Gui* gui; @@ -59,15 +94,26 @@ struct NfcMagicApp { SceneManager* scene_manager; NfcDevice* source_dev; + NfcDevice* target_dev; FuriString* file_name; FuriString* file_path; Nfc* nfc; NfcMagicProtocol protocol; NfcMagicScanner* scanner; + NfcPoller* poller; Gen1aPoller* gen1a_poller; + + Gen2Poller* gen2_poller; + bool gen2_poller_is_wipe_mode; + Gen4Poller* gen4_poller; + NfcMagicAppMfClassicDictAttackContext nfc_dict_context; + DictAttack* dict_attack; + NfcMagicAppWriteProblemsContext write_problems_context; + WriteProblems* write_problems; + uint32_t gen4_password; uint32_t gen4_password_new; @@ -93,6 +139,8 @@ typedef enum { NfcMagicAppViewTextInput, NfcMagicAppViewByteInput, NfcMagicAppViewWidget, + NfcMagicAppViewDictAttack, + NfcMagicAppViewWriteProblems, } NfcMagicAppView; void nfc_magic_app_blink_start(NfcMagicApp* nfc_magic); diff --git a/nfc_magic/scenes/nfc_magic_scene_config.h b/nfc_magic/scenes/nfc_magic_scene_config.h index 301d67f5..71a1315a 100644 --- a/nfc_magic/scenes/nfc_magic_scene_config.h +++ b/nfc_magic/scenes/nfc_magic_scene_config.h @@ -22,3 +22,8 @@ ADD_SCENE(nfc_magic, change_key, ChangeKey) ADD_SCENE(nfc_magic, change_key_fail, ChangeKeyFail) ADD_SCENE(nfc_magic, wrong_card, WrongCard) ADD_SCENE(nfc_magic, not_magic, NotMagic) +ADD_SCENE(nfc_magic, gen2_menu, Gen2Menu) +ADD_SCENE(nfc_magic, mf_classic_menu, MfClassicMenu) +ADD_SCENE(nfc_magic, mf_classic_dict_attack, MfClassicDictAttack) +ADD_SCENE(nfc_magic, gen2_write_check, Gen2WriteCheck) +ADD_SCENE(nfc_magic, mf_classic_write_check, MfClassicWriteCheck) \ No newline at end of file diff --git a/nfc_magic/scenes/nfc_magic_scene_file_select.c b/nfc_magic/scenes/nfc_magic_scene_file_select.c index a13b3e89..20e40a42 100644 --- a/nfc_magic/scenes/nfc_magic_scene_file_select.c +++ b/nfc_magic/scenes/nfc_magic_scene_file_select.c @@ -30,6 +30,14 @@ static bool nfc_magic_scene_file_select_is_file_suitable(NfcMagicApp* instance) (mfu_type != MfUltralightTypeNTAGI2CPlus2K); } } + } else if(instance->protocol == NfcMagicProtocolGen2) { + if(protocol == NfcProtocolMfClassic) { + suitable = true; + } + } else if(instance->protocol == NfcMagicProtocolClassic) { + if(protocol == NfcProtocolMfClassic) { + suitable = true; + } } return suitable; @@ -40,7 +48,13 @@ void nfc_magic_scene_file_select_on_enter(void* context) { if(nfc_magic_load_from_file_select(instance)) { if(nfc_magic_scene_file_select_is_file_suitable(instance)) { - scene_manager_next_scene(instance->scene_manager, NfcMagicSceneWriteConfirm); + if(instance->protocol == NfcMagicProtocolClassic || + instance->protocol == NfcMagicProtocolGen2) { + scene_manager_next_scene( + instance->scene_manager, NfcMagicSceneMfClassicDictAttack); + } else { + scene_manager_next_scene(instance->scene_manager, NfcMagicSceneWriteConfirm); + } } else { scene_manager_next_scene(instance->scene_manager, NfcMagicSceneWrongCard); } diff --git a/nfc_magic/scenes/nfc_magic_scene_gen2_menu.c b/nfc_magic/scenes/nfc_magic_scene_gen2_menu.c new file mode 100644 index 00000000..c28c79c3 --- /dev/null +++ b/nfc_magic/scenes/nfc_magic_scene_gen2_menu.c @@ -0,0 +1,55 @@ +#include "../nfc_magic_app_i.h" + +enum SubmenuIndex { + SubmenuIndexWrite, + SubmenuIndexWipe, +}; + +void nfc_magic_scene_gen2_menu_submenu_callback(void* context, uint32_t index) { + NfcMagicApp* instance = context; + + view_dispatcher_send_custom_event(instance->view_dispatcher, index); +} + +void nfc_magic_scene_gen2_menu_on_enter(void* context) { + NfcMagicApp* instance = context; + + Submenu* submenu = instance->submenu; + submenu_add_item( + submenu, "Write", SubmenuIndexWrite, nfc_magic_scene_gen2_menu_submenu_callback, instance); + submenu_add_item( + submenu, "Wipe", SubmenuIndexWipe, nfc_magic_scene_gen2_menu_submenu_callback, instance); + + submenu_set_selected_item( + submenu, scene_manager_get_scene_state(instance->scene_manager, NfcMagicSceneGen2Menu)); + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcMagicAppViewMenu); +} + +bool nfc_magic_scene_gen2_menu_on_event(void* context, SceneManagerEvent event) { + NfcMagicApp* instance = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubmenuIndexWrite) { + instance->gen2_poller_is_wipe_mode = false; + scene_manager_next_scene(instance->scene_manager, NfcMagicSceneFileSelect); + consumed = true; + } else if(event.event == SubmenuIndexWipe) { + instance->gen2_poller_is_wipe_mode = true; + scene_manager_next_scene(instance->scene_manager, NfcMagicSceneMfClassicDictAttack); + consumed = true; + } + scene_manager_set_scene_state(instance->scene_manager, NfcMagicSceneGen2Menu, event.event); + } else if(event.type == SceneManagerEventTypeBack) { + consumed = scene_manager_search_and_switch_to_previous_scene( + instance->scene_manager, NfcMagicSceneStart); + } + + return consumed; +} + +void nfc_magic_scene_gen2_menu_on_exit(void* context) { + NfcMagicApp* instance = context; + + submenu_reset(instance->submenu); +} diff --git a/nfc_magic/scenes/nfc_magic_scene_gen2_write_check.c b/nfc_magic/scenes/nfc_magic_scene_gen2_write_check.c new file mode 100644 index 00000000..11a38ec5 --- /dev/null +++ b/nfc_magic/scenes/nfc_magic_scene_gen2_write_check.c @@ -0,0 +1,130 @@ +#include "../nfc_magic_app_i.h" + +void nfc_magic_scene_gen2_write_check_view_callback(WriteProblemsEvent event, void* context) { + NfcMagicApp* instance = context; + NfcMagicAppWriteProblemsContext* problems_context = &instance->write_problems_context; + + if(event == WriteProblemsEventCenterPressed) { + if(problems_context->problem_index == problems_context->problems_total - 1) { + // Continue to the next scene + if(instance->gen2_poller_is_wipe_mode) { + scene_manager_next_scene(instance->scene_manager, NfcMagicSceneWipe); + } else { + scene_manager_next_scene(instance->scene_manager, NfcMagicSceneWrite); + } + } else { + // Move to the next problem + problems_context->problem_index++; + problems_context->problem_index_abs++; + write_problems_set_problem_index( + instance->write_problems, problems_context->problem_index); + + for(uint8_t i = problems_context->problem_index_abs; + i < GEN2_POLLER_WRITE_PROBLEMS_LEN; + i++) { + if(problems_context->problems.all_problems & (1 << i)) { + write_problems_set_content(instance->write_problems, gen2_problem_strings[i]); + problems_context->problem_index_abs = i; + break; + } + } + } + } else if(event == WriteProblemsEventLeftPressed) { + if(problems_context->problem_index == 0) { + // Exit to the previous scene + scene_manager_search_and_switch_to_previous_scene( + instance->scene_manager, NfcMagicSceneMfClassicMenu); + } else { + // Move to the previous problem + problems_context->problem_index--; + problems_context->problem_index_abs--; + write_problems_set_problem_index( + instance->write_problems, problems_context->problem_index); + + for(uint8_t i = problems_context->problem_index_abs; + i < GEN2_POLLER_WRITE_PROBLEMS_LEN; + i--) { + if(problems_context->problems.all_problems & (1 << i)) { + write_problems_set_content(instance->write_problems, gen2_problem_strings[i]); + problems_context->problem_index_abs = i; + break; + } + } + } + } +} + +void nfc_magic_scene_gen2_write_check_on_enter(void* context) { + NfcMagicApp* instance = context; + + Gen2PollerWriteProblems problems = gen2_poller_check_target_problems(instance->target_dev); + if(!instance->gen2_poller_is_wipe_mode) { + problems.all_problems |= + gen2_poller_check_source_problems(instance->source_dev).all_problems; + } + + WriteProblems* write_problems = instance->write_problems; + uint8_t problems_count = 0; + uint8_t current_problem = 0; + furi_assert(!problems.no_data, "No MFC data in nfc device"); + + if(problems.all_problems == 0) { + if(instance->gen2_poller_is_wipe_mode) { + scene_manager_next_scene(instance->scene_manager, NfcMagicSceneWipe); + return; + } else { + scene_manager_next_scene(instance->scene_manager, NfcMagicSceneWrite); + return; + } + } + + // Count the total number of problems + for(uint8_t i = 0; i < GEN2_POLLER_WRITE_PROBLEMS_LEN; i++) { + if(problems.all_problems & (1 << i)) { + problems_count++; + } + } + + // Init the view + write_problems_set_callback( + write_problems, nfc_magic_scene_gen2_write_check_view_callback, instance); + write_problems_set_problems_total(write_problems, problems_count); + write_problems_set_problem_index(write_problems, current_problem); + + // Set the initial content to the first problem + for(uint8_t i = 0; i < GEN2_POLLER_WRITE_PROBLEMS_LEN; i++) { + if(problems.all_problems & (1 << i)) { + write_problems_set_content(write_problems, gen2_problem_strings[i]); + current_problem = i; + break; + } + } + + // Save the context + instance->write_problems_context.problem_index = current_problem; + instance->write_problems_context.problems_total = problems_count; + instance->write_problems_context.problems = problems; + + // Setup and start worker + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcMagicAppViewWriteProblems); +} + +bool nfc_magic_scene_gen2_write_check_on_event(void* context, SceneManagerEvent event) { + NfcMagicApp* instance = context; + UNUSED(event); + UNUSED(context); + UNUSED(instance); + bool consumed = false; + + return consumed; +} + +void nfc_magic_scene_gen2_write_check_on_exit(void* context) { + NfcMagicApp* instance = context; + + instance->write_problems_context.problem_index = 0; + instance->write_problems_context.problems_total = 0; + instance->write_problems_context.problems.all_problems = 0; + + write_problems_reset(instance->write_problems); +} diff --git a/nfc_magic/scenes/nfc_magic_scene_magic_info.c b/nfc_magic/scenes/nfc_magic_scene_magic_info.c index a4f08716..d26e8dff 100644 --- a/nfc_magic/scenes/nfc_magic_scene_magic_info.c +++ b/nfc_magic/scenes/nfc_magic_scene_magic_info.c @@ -17,22 +17,27 @@ void nfc_magic_scene_magic_info_on_enter(void* context) { notification_message(instance->notifications, &sequence_success); - widget_add_icon_element(widget, 73, 17, &I_DolphinCommon_56x48); - widget_add_string_element( - widget, 3, 4, AlignLeft, AlignTop, FontPrimary, "Magic card detected"); - widget_add_string_element( - widget, - 3, - 17, - AlignLeft, - AlignTop, - FontSecondary, - nfc_magic_protocols_get_name(instance->protocol)); + FuriString* message = furi_string_alloc(); + + if(instance->protocol == NfcMagicProtocolClassic) { + widget_add_string_element( + widget, 0, 0, AlignLeft, AlignTop, FontPrimary, "It Might Be a Magic Card"); + furi_string_printf(message, "You can make sure the card is\nmagic by writing to it\n"); + } else { + widget_add_string_element( + widget, 0, 0, AlignLeft, AlignTop, FontPrimary, "Magic card detected!"); + } + furi_string_cat_printf( + message, "Magic Type: %s", nfc_magic_protocols_get_name(instance->protocol)); + widget_add_text_box_element( + widget, 0, 10, 128, 54, AlignLeft, AlignTop, furi_string_get_cstr(message), false); widget_add_button_element( widget, GuiButtonTypeLeft, "Retry", nfc_magic_scene_magic_info_widget_callback, instance); widget_add_button_element( widget, GuiButtonTypeRight, "More", nfc_magic_scene_magic_info_widget_callback, instance); + furi_string_free(message); + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcMagicAppViewWidget); } @@ -47,9 +52,15 @@ bool nfc_magic_scene_magic_info_on_event(void* context, SceneManagerEvent event) if(instance->protocol == NfcMagicProtocolGen1) { scene_manager_next_scene(instance->scene_manager, NfcMagicSceneGen1Menu); consumed = true; - } else { + } else if(instance->protocol == NfcMagicProtocolGen4) { scene_manager_next_scene(instance->scene_manager, NfcMagicSceneGen4Menu); consumed = true; + } else if(instance->protocol == NfcMagicProtocolGen2) { + scene_manager_next_scene(instance->scene_manager, NfcMagicSceneGen2Menu); + consumed = true; + } else if(instance->protocol == NfcMagicProtocolClassic) { + scene_manager_next_scene(instance->scene_manager, NfcMagicSceneMfClassicMenu); + consumed = true; } } } diff --git a/nfc_magic/scenes/nfc_magic_scene_mf_classic_dict_attack.c b/nfc_magic/scenes/nfc_magic_scene_mf_classic_dict_attack.c new file mode 100644 index 00000000..c5384198 --- /dev/null +++ b/nfc_magic/scenes/nfc_magic_scene_mf_classic_dict_attack.c @@ -0,0 +1,311 @@ +#include "../nfc_magic_app_i.h" + +#include +#include + +#include "views/dict_attack.h" + +#define TAG "NfcMagicMfClassicDictAttack" + +typedef enum { + DictAttackStateUserDictInProgress, + DictAttackStateSystemDictInProgress, +} DictAttackState; + +NfcCommand nfc_dict_attack_worker_callback(NfcGenericEvent event, void* context) { + furi_assert(context); + furi_assert(event.event_data); + furi_assert(event.instance); + furi_assert(event.protocol == NfcProtocolMfClassic); + + NfcCommand command = NfcCommandContinue; + MfClassicPollerEvent* mfc_event = event.event_data; + + NfcMagicApp* instance = context; + if(mfc_event->type == MfClassicPollerEventTypeCardDetected) { + instance->nfc_dict_context.is_card_present = true; + view_dispatcher_send_custom_event( + instance->view_dispatcher, NfcMagicAppCustomEventCardDetected); + } else if(mfc_event->type == MfClassicPollerEventTypeCardLost) { + instance->nfc_dict_context.is_card_present = false; + view_dispatcher_send_custom_event( + instance->view_dispatcher, NfcMagicAppCustomEventCardLost); + } else if(mfc_event->type == MfClassicPollerEventTypeRequestMode) { + const MfClassicData* mfc_data = nfc_poller_get_data(instance->poller); + nfc_device_set_data(instance->target_dev, NfcProtocolMfClassic, mfc_data); + FURI_LOG_D(TAG, "MFC type: %d", mfc_data->type); + mfc_event->data->poller_mode.mode = MfClassicPollerModeDictAttack; + mfc_event->data->poller_mode.data = mfc_data; + instance->nfc_dict_context.sectors_total = + mf_classic_get_total_sectors_num(mfc_data->type); + FURI_LOG_D(TAG, "Total sectors: %d", mf_classic_get_total_sectors_num(mfc_data->type)); + mf_classic_get_read_sectors_and_keys( + mfc_data, + &instance->nfc_dict_context.sectors_read, + &instance->nfc_dict_context.keys_found); + view_dispatcher_send_custom_event( + instance->view_dispatcher, NfcMagicAppCustomEventDictAttackDataUpdate); + } else if(mfc_event->type == MfClassicPollerEventTypeRequestKey) { + MfClassicKey key = {}; + if(keys_dict_get_next_key( + instance->nfc_dict_context.dict, key.data, sizeof(MfClassicKey))) { + mfc_event->data->key_request_data.key = key; + mfc_event->data->key_request_data.key_provided = true; + instance->nfc_dict_context.dict_keys_current++; + if(instance->nfc_dict_context.dict_keys_current % 10 == 0) { + view_dispatcher_send_custom_event( + instance->view_dispatcher, NfcMagicAppCustomEventDictAttackDataUpdate); + } + } else { + mfc_event->data->key_request_data.key_provided = false; + } + } else if(mfc_event->type == MfClassicPollerEventTypeDataUpdate) { + MfClassicPollerEventDataUpdate* data_update = &mfc_event->data->data_update; + instance->nfc_dict_context.sectors_read = data_update->sectors_read; + instance->nfc_dict_context.keys_found = data_update->keys_found; + instance->nfc_dict_context.current_sector = data_update->current_sector; + view_dispatcher_send_custom_event( + instance->view_dispatcher, NfcMagicAppCustomEventDictAttackDataUpdate); + } else if(mfc_event->type == MfClassicPollerEventTypeNextSector) { + keys_dict_rewind(instance->nfc_dict_context.dict); + instance->nfc_dict_context.dict_keys_current = 0; + instance->nfc_dict_context.current_sector = + mfc_event->data->next_sector_data.current_sector; + view_dispatcher_send_custom_event( + instance->view_dispatcher, NfcMagicAppCustomEventDictAttackDataUpdate); + } else if(mfc_event->type == MfClassicPollerEventTypeFoundKeyA) { + view_dispatcher_send_custom_event( + instance->view_dispatcher, NfcMagicAppCustomEventDictAttackDataUpdate); + } else if(mfc_event->type == MfClassicPollerEventTypeFoundKeyB) { + view_dispatcher_send_custom_event( + instance->view_dispatcher, NfcMagicAppCustomEventDictAttackDataUpdate); + } else if(mfc_event->type == MfClassicPollerEventTypeKeyAttackStart) { + instance->nfc_dict_context.key_attack_current_sector = + mfc_event->data->key_attack_data.current_sector; + instance->nfc_dict_context.is_key_attack = true; + view_dispatcher_send_custom_event( + instance->view_dispatcher, NfcMagicAppCustomEventDictAttackDataUpdate); + } else if(mfc_event->type == MfClassicPollerEventTypeKeyAttackStop) { + keys_dict_rewind(instance->nfc_dict_context.dict); + instance->nfc_dict_context.is_key_attack = false; + instance->nfc_dict_context.dict_keys_current = 0; + view_dispatcher_send_custom_event( + instance->view_dispatcher, NfcMagicAppCustomEventDictAttackDataUpdate); + } else if(mfc_event->type == MfClassicPollerEventTypeSuccess) { + const MfClassicData* mfc_data = nfc_poller_get_data(instance->poller); + nfc_device_set_data(instance->target_dev, NfcProtocolMfClassic, mfc_data); + view_dispatcher_send_custom_event( + instance->view_dispatcher, NfcMagicAppCustomEventDictAttackComplete); + command = NfcCommandStop; + } + + return command; +} + +void nfc_dict_attack_dict_attack_result_callback(DictAttackEvent event, void* context) { + furi_assert(context); + NfcMagicApp* instance = context; + + if(event == DictAttackEventSkipPressed) { + view_dispatcher_send_custom_event( + instance->view_dispatcher, NfcMagicAppCustomEventDictAttackSkip); + } +} + +static void nfc_magic_scene_mf_classic_dict_attack_update_view(NfcMagicApp* instance) { + NfcMagicAppMfClassicDictAttackContext* mfc_dict = &instance->nfc_dict_context; + + if(mfc_dict->is_key_attack) { + dict_attack_set_key_attack(instance->dict_attack, mfc_dict->key_attack_current_sector); + } else { + dict_attack_reset_key_attack(instance->dict_attack); + dict_attack_set_sectors_total(instance->dict_attack, mfc_dict->sectors_total); + dict_attack_set_sectors_read(instance->dict_attack, mfc_dict->sectors_read); + dict_attack_set_keys_found(instance->dict_attack, mfc_dict->keys_found); + dict_attack_set_current_dict_key(instance->dict_attack, mfc_dict->dict_keys_current); + dict_attack_set_current_sector(instance->dict_attack, mfc_dict->current_sector); + } +} + +static void nfc_magic_scene_mf_classic_dict_attack_prepare_view(NfcMagicApp* instance) { + uint32_t state = + scene_manager_get_scene_state(instance->scene_manager, NfcMagicSceneMfClassicDictAttack); + if(state == DictAttackStateUserDictInProgress) { + do { + if(!keys_dict_check_presence(NFC_APP_MF_CLASSIC_DICT_USER_PATH)) { + state = DictAttackStateSystemDictInProgress; + break; + } + + instance->nfc_dict_context.dict = keys_dict_alloc( + NFC_APP_MF_CLASSIC_DICT_USER_PATH, KeysDictModeOpenAlways, sizeof(MfClassicKey)); + if(keys_dict_get_total_keys(instance->nfc_dict_context.dict) == 0) { + keys_dict_free(instance->nfc_dict_context.dict); + state = DictAttackStateSystemDictInProgress; + break; + } + + dict_attack_set_header(instance->dict_attack, "MF Classic User Dictionary"); + } while(false); + } + if(state == DictAttackStateSystemDictInProgress) { + instance->nfc_dict_context.dict = keys_dict_alloc( + NFC_APP_MF_CLASSIC_DICT_SYSTEM_PATH, KeysDictModeOpenExisting, sizeof(MfClassicKey)); + dict_attack_set_header(instance->dict_attack, "MF Classic System Dictionary"); + } + + instance->nfc_dict_context.dict_keys_total = + keys_dict_get_total_keys(instance->nfc_dict_context.dict); + dict_attack_set_total_dict_keys( + instance->dict_attack, instance->nfc_dict_context.dict_keys_total); + instance->nfc_dict_context.dict_keys_current = 0; + + dict_attack_set_callback( + instance->dict_attack, nfc_dict_attack_dict_attack_result_callback, instance); + nfc_magic_scene_mf_classic_dict_attack_update_view(instance); + + scene_manager_set_scene_state( + instance->scene_manager, NfcMagicSceneMfClassicDictAttack, state); +} + +void nfc_magic_scene_mf_classic_dict_attack_on_enter(void* context) { + NfcMagicApp* instance = context; + + scene_manager_set_scene_state( + instance->scene_manager, + NfcMagicSceneMfClassicDictAttack, + DictAttackStateUserDictInProgress); + nfc_magic_scene_mf_classic_dict_attack_prepare_view(instance); + dict_attack_set_card_state(instance->dict_attack, true); + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcMagicAppViewDictAttack); + nfc_magic_app_blink_start(instance); + notification_message(instance->notifications, &sequence_display_backlight_enforce_on); + + instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfClassic); + nfc_poller_start(instance->poller, nfc_dict_attack_worker_callback, instance); +} + +static void nfc_magic_scene_mf_classic_dict_attack_notify_read(NfcMagicApp* instance) { + const MfClassicData* mfc_data = nfc_poller_get_data(instance->poller); + bool is_card_fully_read = mf_classic_is_card_read(mfc_data); + if(is_card_fully_read) { + notification_message(instance->notifications, &sequence_success); + } else { + notification_message(instance->notifications, &sequence_semi_success); + } +} + +bool nfc_magic_scene_mf_classic_dict_attack_on_event(void* context, SceneManagerEvent event) { + NfcMagicApp* instance = context; + bool consumed = false; + + uint32_t state = + scene_manager_get_scene_state(instance->scene_manager, NfcMagicSceneMfClassicDictAttack); + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == NfcMagicAppCustomEventDictAttackComplete) { + if(state == DictAttackStateUserDictInProgress) { + nfc_poller_stop(instance->poller); + nfc_poller_free(instance->poller); + keys_dict_free(instance->nfc_dict_context.dict); + scene_manager_set_scene_state( + instance->scene_manager, + NfcMagicSceneMfClassicDictAttack, + DictAttackStateSystemDictInProgress); + nfc_magic_scene_mf_classic_dict_attack_prepare_view(instance); + instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfClassic); + nfc_poller_start(instance->poller, nfc_dict_attack_worker_callback, instance); + consumed = true; + } else { + nfc_magic_scene_mf_classic_dict_attack_notify_read(instance); + if(instance->protocol == NfcMagicProtocolGen2) { + scene_manager_next_scene(instance->scene_manager, NfcMagicSceneGen2WriteCheck); + } else { + scene_manager_next_scene( + instance->scene_manager, NfcMagicSceneMfClassicWriteCheck); + } + dolphin_deed(DolphinDeedNfcReadSuccess); + consumed = true; + } + } else if(event.event == NfcMagicAppCustomEventCardDetected) { + dict_attack_set_card_state(instance->dict_attack, true); + consumed = true; + } else if(event.event == NfcMagicAppCustomEventCardLost) { + dict_attack_set_card_state(instance->dict_attack, false); + consumed = true; + } else if(event.event == NfcMagicAppCustomEventDictAttackDataUpdate) { + nfc_magic_scene_mf_classic_dict_attack_update_view(instance); + } else if(event.event == NfcMagicAppCustomEventDictAttackSkip) { + const MfClassicData* mfc_data = nfc_poller_get_data(instance->poller); + nfc_device_set_data(instance->target_dev, NfcProtocolMfClassic, mfc_data); + if(state == DictAttackStateUserDictInProgress) { + if(instance->nfc_dict_context.is_card_present) { + nfc_poller_stop(instance->poller); + nfc_poller_free(instance->poller); + keys_dict_free(instance->nfc_dict_context.dict); + scene_manager_set_scene_state( + instance->scene_manager, + NfcMagicSceneMfClassicDictAttack, + DictAttackStateSystemDictInProgress); + nfc_magic_scene_mf_classic_dict_attack_prepare_view(instance); + instance->poller = nfc_poller_alloc(instance->nfc, NfcProtocolMfClassic); + nfc_poller_start(instance->poller, nfc_dict_attack_worker_callback, instance); + } else { + nfc_magic_scene_mf_classic_dict_attack_notify_read(instance); + if(instance->protocol == NfcMagicProtocolGen2) { + scene_manager_next_scene( + instance->scene_manager, NfcMagicSceneGen2WriteCheck); + } else { + scene_manager_next_scene( + instance->scene_manager, NfcMagicSceneMfClassicWriteCheck); + } + dolphin_deed(DolphinDeedNfcReadSuccess); + } + consumed = true; + } else if(state == DictAttackStateSystemDictInProgress) { + nfc_magic_scene_mf_classic_dict_attack_notify_read(instance); + if(instance->protocol == NfcMagicProtocolGen2) { + scene_manager_next_scene(instance->scene_manager, NfcMagicSceneGen2WriteCheck); + } else { + scene_manager_next_scene( + instance->scene_manager, NfcMagicSceneMfClassicWriteCheck); + } + dolphin_deed(DolphinDeedNfcReadSuccess); + consumed = true; + } + } + } else if(event.type == SceneManagerEventTypeBack) { + scene_manager_previous_scene(instance->scene_manager); + consumed = true; + } + return consumed; +} + +void nfc_magic_scene_mf_classic_dict_attack_on_exit(void* context) { + NfcMagicApp* instance = context; + const MfClassicData* mfc_data = nfc_poller_get_data(instance->poller); + nfc_device_set_data(instance->target_dev, NfcProtocolMfClassic, mfc_data); + + nfc_poller_stop(instance->poller); + nfc_poller_free(instance->poller); + + dict_attack_reset(instance->dict_attack); + scene_manager_set_scene_state( + instance->scene_manager, + NfcMagicSceneMfClassicDictAttack, + DictAttackStateUserDictInProgress); + + keys_dict_free(instance->nfc_dict_context.dict); + + instance->nfc_dict_context.current_sector = 0; + instance->nfc_dict_context.sectors_total = 0; + instance->nfc_dict_context.sectors_read = 0; + instance->nfc_dict_context.keys_found = 0; + instance->nfc_dict_context.dict_keys_total = 0; + instance->nfc_dict_context.dict_keys_current = 0; + instance->nfc_dict_context.is_key_attack = false; + instance->nfc_dict_context.key_attack_current_sector = 0; + instance->nfc_dict_context.is_card_present = false; + + nfc_magic_app_blink_stop(instance); + notification_message(instance->notifications, &sequence_display_backlight_enforce_auto); +} diff --git a/nfc_magic/scenes/nfc_magic_scene_mf_classic_menu.c b/nfc_magic/scenes/nfc_magic_scene_mf_classic_menu.c new file mode 100644 index 00000000..a1237022 --- /dev/null +++ b/nfc_magic/scenes/nfc_magic_scene_mf_classic_menu.c @@ -0,0 +1,65 @@ +#include "../nfc_magic_app_i.h" + +enum SubmenuIndex { + SubmenuIndexWrite, + SubmenuIndexWipe, +}; + +void nfc_magic_scene_mf_classic_menu_submenu_callback(void* context, uint32_t index) { + NfcMagicApp* instance = context; + + view_dispatcher_send_custom_event(instance->view_dispatcher, index); +} + +void nfc_magic_scene_mf_classic_menu_on_enter(void* context) { + NfcMagicApp* instance = context; + + Submenu* submenu = instance->submenu; + submenu_add_item( + submenu, + "Write", + SubmenuIndexWrite, + nfc_magic_scene_mf_classic_menu_submenu_callback, + instance); + submenu_add_item( + submenu, + "Wipe", + SubmenuIndexWipe, + nfc_magic_scene_mf_classic_menu_submenu_callback, + instance); + + submenu_set_selected_item( + submenu, + scene_manager_get_scene_state(instance->scene_manager, NfcMagicSceneMfClassicMenu)); + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcMagicAppViewMenu); +} + +bool nfc_magic_scene_mf_classic_menu_on_event(void* context, SceneManagerEvent event) { + NfcMagicApp* instance = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + if(event.event == SubmenuIndexWrite) { + instance->gen2_poller_is_wipe_mode = false; + scene_manager_next_scene(instance->scene_manager, NfcMagicSceneFileSelect); + consumed = true; + } else if(event.event == SubmenuIndexWipe) { + instance->gen2_poller_is_wipe_mode = true; + scene_manager_next_scene(instance->scene_manager, NfcMagicSceneMfClassicDictAttack); + consumed = true; + } + scene_manager_set_scene_state( + instance->scene_manager, NfcMagicSceneMfClassicMenu, event.event); + } else if(event.type == SceneManagerEventTypeBack) { + consumed = scene_manager_search_and_switch_to_previous_scene( + instance->scene_manager, NfcMagicSceneStart); + } + + return consumed; +} + +void nfc_magic_scene_mf_classic_menu_on_exit(void* context) { + NfcMagicApp* instance = context; + + submenu_reset(instance->submenu); +} diff --git a/nfc_magic/scenes/nfc_magic_scene_mf_classic_write_check.c b/nfc_magic/scenes/nfc_magic_scene_mf_classic_write_check.c new file mode 100644 index 00000000..4694733e --- /dev/null +++ b/nfc_magic/scenes/nfc_magic_scene_mf_classic_write_check.c @@ -0,0 +1,124 @@ +#include "../nfc_magic_app_i.h" + +void nfc_magic_scene_mf_classic_write_check_view_callback(WriteProblemsEvent event, void* context) { + NfcMagicApp* instance = context; + NfcMagicAppWriteProblemsContext* problems_context = &instance->write_problems_context; + + if(event == WriteProblemsEventCenterPressed) { + if(problems_context->problem_index == problems_context->problems_total - 1) { + // Continue to the next scene + if(instance->gen2_poller_is_wipe_mode) { + scene_manager_next_scene(instance->scene_manager, NfcMagicSceneWipe); + } else { + scene_manager_next_scene(instance->scene_manager, NfcMagicSceneWrite); + } + } else { + // Move to the next problem + problems_context->problem_index++; + problems_context->problem_index_abs++; + write_problems_set_problem_index( + instance->write_problems, problems_context->problem_index); + + for(uint8_t i = problems_context->problem_index_abs; + i < GEN2_POLLER_WRITE_PROBLEMS_LEN; + i++) { + if(problems_context->problems.all_problems & (1 << i)) { + write_problems_set_content(instance->write_problems, gen2_problem_strings[i]); + problems_context->problem_index_abs = i; + break; + } + } + } + } else if(event == WriteProblemsEventLeftPressed) { + if(problems_context->problem_index == 0) { + // Exit to the previous scene + scene_manager_search_and_switch_to_previous_scene( + instance->scene_manager, NfcMagicSceneMfClassicMenu); + } else { + // Move to the previous problem + problems_context->problem_index--; + problems_context->problem_index_abs--; + write_problems_set_problem_index( + instance->write_problems, problems_context->problem_index); + + for(uint8_t i = problems_context->problem_index_abs; + i < GEN2_POLLER_WRITE_PROBLEMS_LEN; + i--) { + if(problems_context->problems.all_problems & (1 << i)) { + write_problems_set_content(instance->write_problems, gen2_problem_strings[i]); + problems_context->problem_index_abs = i; + break; + } + } + } + } +} + +void nfc_magic_scene_mf_classic_write_check_on_enter(void* context) { + NfcMagicApp* instance = context; + + Gen2PollerWriteProblems problems = gen2_poller_check_target_problems(instance->target_dev); + if(!instance->gen2_poller_is_wipe_mode) { + problems.all_problems |= + gen2_poller_check_source_problems(instance->source_dev).all_problems; + } + FURI_LOG_D("GEN2", "Problems: %d", problems.all_problems); + + WriteProblems* write_problems = instance->write_problems; + uint8_t problems_count = 0; + uint8_t current_problem = 0; + furi_assert(!problems.no_data, "No MFC data in nfc device"); + + // Set the uid_locked problem to true as we have a Mifare Classic card + problems.uid_locked = true; + + // Count the number of problems + for(uint8_t i = 0; i < GEN2_POLLER_WRITE_PROBLEMS_LEN; i++) { + if(problems.all_problems & (1 << i)) { + problems_count++; + } + } + + // Init the view + write_problems_set_callback( + write_problems, nfc_magic_scene_mf_classic_write_check_view_callback, instance); + write_problems_set_problems_total(write_problems, problems_count); + write_problems_set_problem_index(write_problems, current_problem); + + // Set the first problem + for(uint8_t i = current_problem; i < GEN2_POLLER_WRITE_PROBLEMS_LEN; i++) { + if(problems.all_problems & (1 << i)) { + write_problems_set_content(instance->write_problems, gen2_problem_strings[i]); + instance->write_problems_context.problem_index_abs = i; + break; + } + } + + // Save the problems context + instance->write_problems_context.problem_index = current_problem; + instance->write_problems_context.problems_total = problems_count; + instance->write_problems_context.problems = problems; + + // Setup and start worker + view_dispatcher_switch_to_view(instance->view_dispatcher, NfcMagicAppViewWriteProblems); +} + +bool nfc_magic_scene_mf_classic_write_check_on_event(void* context, SceneManagerEvent event) { + NfcMagicApp* instance = context; + UNUSED(event); + UNUSED(context); + UNUSED(instance); + bool consumed = false; + + return consumed; +} + +void nfc_magic_scene_mf_classic_write_check_on_exit(void* context) { + NfcMagicApp* instance = context; + + instance->write_problems_context.problem_index = 0; + instance->write_problems_context.problems_total = 0; + instance->write_problems_context.problems.all_problems = 0; + + write_problems_reset(instance->write_problems); +} diff --git a/nfc_magic/scenes/nfc_magic_scene_success.c b/nfc_magic/scenes/nfc_magic_scene_success.c index 0cce06c6..5a264d12 100644 --- a/nfc_magic/scenes/nfc_magic_scene_success.c +++ b/nfc_magic/scenes/nfc_magic_scene_success.c @@ -11,8 +11,8 @@ void nfc_magic_scene_success_on_enter(void* context) { notification_message(instance->notifications, &sequence_success); Popup* popup = instance->popup; - popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59); - popup_set_header(popup, "Success!", 10, 20, AlignLeft, AlignBottom); + popup_set_icon(popup, 0, 9, &I_DolphinSuccess_91x55); + popup_set_header(popup, "Success!", 75, 12, AlignLeft, AlignCenter); popup_set_timeout(popup, 1500); popup_set_context(popup, instance); popup_set_callback(popup, nfc_magic_scene_success_popup_callback); diff --git a/nfc_magic/scenes/nfc_magic_scene_wipe.c b/nfc_magic/scenes/nfc_magic_scene_wipe.c index 3cf1f149..f04550b7 100644 --- a/nfc_magic/scenes/nfc_magic_scene_wipe.c +++ b/nfc_magic/scenes/nfc_magic_scene_wipe.c @@ -5,7 +5,7 @@ enum { NfcMagicSceneWipeStateCardFound, }; -NfcCommand nfc_mafic_scene_wipe_gen1_poller_callback(Gen1aPollerEvent event, void* context) { +NfcCommand nfc_magic_scene_wipe_gen1_poller_callback(Gen1aPollerEvent event, void* context) { NfcMagicApp* instance = context; furi_assert(event.data); @@ -29,7 +29,35 @@ NfcCommand nfc_mafic_scene_wipe_gen1_poller_callback(Gen1aPollerEvent event, voi return command; } -NfcCommand nfc_mafic_scene_wipe_gen4_poller_callback(Gen4PollerEvent event, void* context) { +NfcCommand nfc_magic_scene_wipe_gen2_poller_callback(Gen2PollerEvent event, void* context) { + NfcMagicApp* instance = context; + + NfcCommand command = NfcCommandContinue; + + if(event.type == Gen2PollerEventTypeDetected) { + view_dispatcher_send_custom_event( + instance->view_dispatcher, NfcMagicCustomEventCardDetected); + } else if(event.type == Gen2PollerEventTypeRequestMode) { + event.data->poller_mode.mode = Gen2PollerModeWipe; + } else if(event.type == Gen2PollerEventTypeRequestTargetData) { + const MfClassicData* mfc_data = + nfc_device_get_data(instance->target_dev, NfcProtocolMfClassic); + event.data->target_data.mfc_data = mfc_data; + + } else if(event.type == Gen2PollerEventTypeSuccess) { + view_dispatcher_send_custom_event( + instance->view_dispatcher, NfcMagicCustomEventWorkerSuccess); + command = NfcCommandStop; + } else if(event.type == Gen2PollerEventTypeFail) { + view_dispatcher_send_custom_event( + instance->view_dispatcher, NfcMagicCustomEventWorkerFail); + command = NfcCommandStop; + } + + return command; +} + +NfcCommand nfc_magic_scene_wipe_gen4_poller_callback(Gen4PollerEvent event, void* context) { NfcMagicApp* instance = context; NfcCommand command = NfcCommandContinue; @@ -81,12 +109,20 @@ void nfc_magic_scene_wipe_on_enter(void* context) { if(instance->protocol == NfcMagicProtocolGen1) { instance->gen1a_poller = gen1a_poller_alloc(instance->nfc); gen1a_poller_start( - instance->gen1a_poller, nfc_mafic_scene_wipe_gen1_poller_callback, instance); - } else { + instance->gen1a_poller, nfc_magic_scene_wipe_gen1_poller_callback, instance); + } else if(instance->protocol == NfcMagicProtocolGen2) { + instance->gen2_poller = gen2_poller_alloc(instance->nfc); + gen2_poller_start( + instance->gen2_poller, nfc_magic_scene_wipe_gen2_poller_callback, instance); + } else if(instance->protocol == NfcMagicProtocolClassic) { + instance->gen2_poller = gen2_poller_alloc(instance->nfc); + gen2_poller_start( + instance->gen2_poller, nfc_magic_scene_wipe_gen2_poller_callback, instance); + } else if(instance->protocol == NfcMagicProtocolGen4) { instance->gen4_poller = gen4_poller_alloc(instance->nfc); gen4_poller_set_password(instance->gen4_poller, instance->gen4_password); gen4_poller_start( - instance->gen4_poller, nfc_mafic_scene_wipe_gen4_poller_callback, instance); + instance->gen4_poller, nfc_magic_scene_wipe_gen4_poller_callback, instance); } } @@ -123,7 +159,13 @@ void nfc_magic_scene_wipe_on_exit(void* context) { if(instance->protocol == NfcMagicProtocolGen1) { gen1a_poller_stop(instance->gen1a_poller); gen1a_poller_free(instance->gen1a_poller); - } else { + } else if(instance->protocol == NfcMagicProtocolGen2) { + gen2_poller_stop(instance->gen2_poller); + gen2_poller_free(instance->gen2_poller); + } else if(instance->protocol == NfcMagicProtocolClassic) { + gen2_poller_stop(instance->gen2_poller); + gen2_poller_free(instance->gen2_poller); + } else if(instance->protocol == NfcMagicProtocolGen4) { gen4_poller_stop(instance->gen4_poller); gen4_poller_free(instance->gen4_poller); } diff --git a/nfc_magic/scenes/nfc_magic_scene_wipe_fail.c b/nfc_magic/scenes/nfc_magic_scene_wipe_fail.c index c3591e4f..895b162d 100644 --- a/nfc_magic/scenes/nfc_magic_scene_wipe_fail.c +++ b/nfc_magic/scenes/nfc_magic_scene_wipe_fail.c @@ -14,8 +14,11 @@ void nfc_magic_scene_wipe_fail_on_enter(void* context) { Widget* widget = instance->widget; notification_message(instance->notifications, &sequence_error); - widget_add_icon_element(widget, 73, 17, &I_DolphinCommon_56x48); - widget_add_string_element(widget, 3, 4, AlignLeft, AlignTop, FontPrimary, "Wipe failed"); + widget_add_icon_element(widget, 83, 22, &I_WarningDolphinFlip_45x42); + widget_add_string_element(widget, 64, 0, AlignCenter, AlignTop, FontPrimary, "Failed to Wipe"); + widget_add_string_multiline_element( + widget, 0, 13, AlignLeft, AlignTop, FontSecondary, "Something went\nwrong while wiping"); + widget_add_button_element( widget, GuiButtonTypeLeft, "Retry", nfc_magic_scene_wipe_fail_widget_callback, instance); diff --git a/nfc_magic/scenes/nfc_magic_scene_write.c b/nfc_magic/scenes/nfc_magic_scene_write.c index e5705748..af8c3905 100644 --- a/nfc_magic/scenes/nfc_magic_scene_write.c +++ b/nfc_magic/scenes/nfc_magic_scene_write.c @@ -5,7 +5,7 @@ enum { NfcMagicSceneWriteStateCardFound, }; -NfcCommand nfc_mafic_scene_write_gen1_poller_callback(Gen1aPollerEvent event, void* context) { +NfcCommand nfc_magic_scene_write_gen1_poller_callback(Gen1aPollerEvent event, void* context) { NfcMagicApp* instance = context; furi_assert(event.data); @@ -33,7 +33,39 @@ NfcCommand nfc_mafic_scene_write_gen1_poller_callback(Gen1aPollerEvent event, vo return command; } -NfcCommand nfc_mafic_scene_write_gen4_poller_callback(Gen4PollerEvent event, void* context) { +NfcCommand nfc_magic_scene_write_gen2_poller_callback(Gen2PollerEvent event, void* context) { + NfcMagicApp* instance = context; + furi_assert(event.data); + + NfcCommand command = NfcCommandContinue; + + if(event.type == Gen2PollerEventTypeDetected) { + view_dispatcher_send_custom_event( + instance->view_dispatcher, NfcMagicCustomEventCardDetected); + } else if(event.type == Gen2PollerEventTypeRequestMode) { + event.data->poller_mode.mode = Gen2PollerModeWrite; + } else if(event.type == Gen2PollerEventTypeRequestDataToWrite) { + const MfClassicData* mfc_data = + nfc_device_get_data(instance->source_dev, NfcProtocolMfClassic); + event.data->data_to_write.mfc_data = mfc_data; + } else if(event.type == Gen2PollerEventTypeRequestTargetData) { + const MfClassicData* mfc_data = + nfc_device_get_data(instance->target_dev, NfcProtocolMfClassic); + event.data->target_data.mfc_data = mfc_data; + } else if(event.type == Gen2PollerEventTypeSuccess) { + view_dispatcher_send_custom_event( + instance->view_dispatcher, NfcMagicCustomEventWorkerSuccess); + command = NfcCommandStop; + } else if(event.type == Gen2PollerEventTypeFail) { + view_dispatcher_send_custom_event( + instance->view_dispatcher, NfcMagicCustomEventWorkerFail); + command = NfcCommandStop; + } + + return command; +} + +NfcCommand nfc_magic_scene_write_gen4_poller_callback(Gen4PollerEvent event, void* context) { NfcMagicApp* instance = context; furi_assert(event.data); @@ -90,12 +122,20 @@ void nfc_magic_scene_write_on_enter(void* context) { if(instance->protocol == NfcMagicProtocolGen1) { instance->gen1a_poller = gen1a_poller_alloc(instance->nfc); gen1a_poller_start( - instance->gen1a_poller, nfc_mafic_scene_write_gen1_poller_callback, instance); + instance->gen1a_poller, nfc_magic_scene_write_gen1_poller_callback, instance); + } else if(instance->protocol == NfcMagicProtocolGen2) { + instance->gen2_poller = gen2_poller_alloc(instance->nfc); + gen2_poller_start( + instance->gen2_poller, nfc_magic_scene_write_gen2_poller_callback, instance); + } else if(instance->protocol == NfcMagicProtocolClassic) { + instance->gen2_poller = gen2_poller_alloc(instance->nfc); + gen2_poller_start( + instance->gen2_poller, nfc_magic_scene_write_gen2_poller_callback, instance); } else { instance->gen4_poller = gen4_poller_alloc(instance->nfc); gen4_poller_set_password(instance->gen4_poller, instance->gen4_password); gen4_poller_start( - instance->gen4_poller, nfc_mafic_scene_write_gen4_poller_callback, instance); + instance->gen4_poller, nfc_magic_scene_write_gen4_poller_callback, instance); } } @@ -132,7 +172,12 @@ void nfc_magic_scene_write_on_exit(void* context) { if(instance->protocol == NfcMagicProtocolGen1) { gen1a_poller_stop(instance->gen1a_poller); gen1a_poller_free(instance->gen1a_poller); - } else { + } else if( + instance->protocol == NfcMagicProtocolGen2 || + instance->protocol == NfcMagicProtocolClassic) { + gen2_poller_stop(instance->gen2_poller); + gen2_poller_free(instance->gen2_poller); + } else if(instance->protocol == NfcMagicProtocolGen4) { gen4_poller_stop(instance->gen4_poller); gen4_poller_free(instance->gen4_poller); } diff --git a/nfc_magic/scenes/nfc_magic_scene_write_fail.c b/nfc_magic/scenes/nfc_magic_scene_write_fail.c index 8836ad45..042b8098 100644 --- a/nfc_magic/scenes/nfc_magic_scene_write_fail.c +++ b/nfc_magic/scenes/nfc_magic_scene_write_fail.c @@ -16,20 +16,14 @@ void nfc_magic_scene_write_fail_on_enter(void* context) { notification_message(instance->notifications, &sequence_error); - widget_add_icon_element(widget, 72, 17, &I_DolphinCommon_56x48); + widget_add_icon_element(widget, 83, 22, &I_WarningDolphinFlip_45x42); widget_add_string_element( - widget, 7, 4, AlignLeft, AlignTop, FontPrimary, "Writing gone wrong!"); + widget, 64, 0, AlignCenter, AlignTop, FontPrimary, "Failed to Write"); widget_add_string_multiline_element( - widget, - 7, - 17, - AlignLeft, - AlignTop, - FontSecondary, - "Not all sectors\nwere written\ncorrectly."); + widget, 0, 13, AlignLeft, AlignTop, FontSecondary, "Something went\nwrong while\nwriting"); widget_add_button_element( - widget, GuiButtonTypeLeft, "Finish", nfc_magic_scene_write_fail_widget_callback, instance); + widget, GuiButtonTypeLeft, "Retry", nfc_magic_scene_write_fail_widget_callback, instance); // Setup and start worker view_dispatcher_switch_to_view(instance->view_dispatcher, NfcMagicAppViewWidget); @@ -41,12 +35,10 @@ bool nfc_magic_scene_write_fail_on_event(void* context, SceneManagerEvent event) if(event.type == SceneManagerEventTypeCustom) { if(event.event == GuiButtonTypeLeft) { - consumed = scene_manager_search_and_switch_to_previous_scene( - instance->scene_manager, NfcMagicSceneStart); + consumed = scene_manager_previous_scene(instance->scene_manager); } } else if(event.type == SceneManagerEventTypeBack) { - consumed = scene_manager_search_and_switch_to_previous_scene( - instance->scene_manager, NfcMagicSceneStart); + consumed = scene_manager_previous_scene(instance->scene_manager); } return consumed; } diff --git a/nfc_magic/views/dict_attack.c b/nfc_magic/views/dict_attack.c new file mode 100644 index 00000000..f09ca0c7 --- /dev/null +++ b/nfc_magic/views/dict_attack.c @@ -0,0 +1,245 @@ +#include "dict_attack.h" + +#include + +#define NFC_CLASSIC_KEYS_PER_SECTOR 2 + +struct DictAttack { + View* view; + DictAttackCallback callback; + void* context; +}; + +typedef struct { + FuriString* header; + bool card_detected; + uint8_t sectors_total; + uint8_t sectors_read; + uint8_t current_sector; + uint8_t keys_found; + size_t dict_keys_total; + size_t dict_keys_current; + bool is_key_attack; + uint8_t key_attack_current_sector; +} DictAttackViewModel; + +static void dict_attack_draw_callback(Canvas* canvas, void* model) { + DictAttackViewModel* m = model; + if(!m->card_detected) { + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned( + canvas, 64, 4, AlignCenter, AlignTop, "Hold the tag to the Flipper!"); + canvas_set_font(canvas, FontSecondary); + elements_multiline_text_aligned( + canvas, 64, 23, AlignCenter, AlignTop, "Make sure the tag is\npositioned correctly."); + } else { + char draw_str[32] = {}; + canvas_set_font(canvas, FontSecondary); + canvas_draw_str_aligned( + canvas, 64, 0, AlignCenter, AlignTop, furi_string_get_cstr(m->header)); + if(m->is_key_attack) { + snprintf( + draw_str, + sizeof(draw_str), + "Reuse key check for sector: %d", + m->key_attack_current_sector); + } else { + snprintf(draw_str, sizeof(draw_str), "Unlocking sector: %d", m->current_sector); + } + canvas_draw_str_aligned(canvas, 0, 10, AlignLeft, AlignTop, draw_str); + float dict_progress = m->dict_keys_total == 0 ? + 0 : + (float)(m->dict_keys_current) / (float)(m->dict_keys_total); + float progress = m->sectors_total == 0 ? 0 : + ((float)(m->current_sector) + dict_progress) / + (float)(m->sectors_total); + if(progress > 1.0f) { + progress = 1.0f; + } + if(m->dict_keys_current == 0) { + // Cause when people see 0 they think it's broken + snprintf(draw_str, sizeof(draw_str), "%d/%zu", 1, m->dict_keys_total); + } else { + snprintf( + draw_str, sizeof(draw_str), "%zu/%zu", m->dict_keys_current, m->dict_keys_total); + } + elements_progress_bar_with_text(canvas, 0, 20, 128, dict_progress, draw_str); + canvas_set_font(canvas, FontSecondary); + snprintf( + draw_str, + sizeof(draw_str), + "Keys found: %d/%d", + m->keys_found, + m->sectors_total * NFC_CLASSIC_KEYS_PER_SECTOR); + canvas_draw_str_aligned(canvas, 0, 33, AlignLeft, AlignTop, draw_str); + snprintf( + draw_str, sizeof(draw_str), "Sectors Read: %d/%d", m->sectors_read, m->sectors_total); + canvas_draw_str_aligned(canvas, 0, 43, AlignLeft, AlignTop, draw_str); + } + elements_button_center(canvas, "Skip"); +} + +static bool dict_attack_input_callback(InputEvent* event, void* context) { + DictAttack* instance = context; + bool consumed = false; + + if(event->type == InputTypeShort && event->key == InputKeyOk) { + if(instance->callback) { + instance->callback(DictAttackEventSkipPressed, instance->context); + } + consumed = true; + } + + return consumed; +} + +DictAttack* dict_attack_alloc() { + DictAttack* instance = malloc(sizeof(DictAttack)); + instance->view = view_alloc(); + view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(DictAttackViewModel)); + view_set_draw_callback(instance->view, dict_attack_draw_callback); + view_set_input_callback(instance->view, dict_attack_input_callback); + view_set_context(instance->view, instance); + with_view_model( + instance->view, + DictAttackViewModel * model, + { model->header = furi_string_alloc(); }, + false); + + return instance; +} + +void dict_attack_free(DictAttack* instance) { + furi_assert(instance); + + with_view_model( + instance->view, DictAttackViewModel * model, { furi_string_free(model->header); }, false); + + view_free(instance->view); + free(instance); +} + +void dict_attack_reset(DictAttack* instance) { + furi_assert(instance); + + with_view_model( + instance->view, + DictAttackViewModel * model, + { + model->sectors_total = 0; + model->sectors_read = 0; + model->current_sector = 0; + model->keys_found = 0; + model->dict_keys_total = 0; + model->dict_keys_current = 0; + model->is_key_attack = false; + furi_string_reset(model->header); + }, + false); +} + +View* dict_attack_get_view(DictAttack* instance) { + furi_assert(instance); + + return instance->view; +} + +void dict_attack_set_callback(DictAttack* instance, DictAttackCallback callback, void* context) { + furi_assert(instance); + furi_assert(callback); + + instance->callback = callback; + instance->context = context; +} + +void dict_attack_set_header(DictAttack* instance, const char* header) { + furi_assert(instance); + furi_assert(header); + + with_view_model( + instance->view, + DictAttackViewModel * model, + { furi_string_set(model->header, header); }, + true); +} + +void dict_attack_set_card_state(DictAttack* instance, bool detected) { + furi_assert(instance); + + with_view_model( + instance->view, DictAttackViewModel * model, { model->card_detected = detected; }, true); +} + +void dict_attack_set_sectors_total(DictAttack* instance, uint8_t sectors_total) { + furi_assert(instance); + + with_view_model( + instance->view, + DictAttackViewModel * model, + { model->sectors_total = sectors_total; }, + true); +} + +void dict_attack_set_sectors_read(DictAttack* instance, uint8_t sectors_read) { + furi_assert(instance); + + with_view_model( + instance->view, DictAttackViewModel * model, { model->sectors_read = sectors_read; }, true); +} + +void dict_attack_set_keys_found(DictAttack* instance, uint8_t keys_found) { + furi_assert(instance); + + with_view_model( + instance->view, DictAttackViewModel * model, { model->keys_found = keys_found; }, true); +} + +void dict_attack_set_current_sector(DictAttack* instance, uint8_t current_sector) { + furi_assert(instance); + + with_view_model( + instance->view, + DictAttackViewModel * model, + { model->current_sector = current_sector; }, + true); +} + +void dict_attack_set_total_dict_keys(DictAttack* instance, size_t dict_keys_total) { + furi_assert(instance); + + with_view_model( + instance->view, + DictAttackViewModel * model, + { model->dict_keys_total = dict_keys_total; }, + true); +} + +void dict_attack_set_current_dict_key(DictAttack* instance, size_t cur_key_num) { + furi_assert(instance); + + with_view_model( + instance->view, + DictAttackViewModel * model, + { model->dict_keys_current = cur_key_num; }, + true); +} + +void dict_attack_set_key_attack(DictAttack* instance, uint8_t sector) { + furi_assert(instance); + + with_view_model( + instance->view, + DictAttackViewModel * model, + { + model->is_key_attack = true; + model->key_attack_current_sector = sector; + }, + true); +} + +void dict_attack_reset_key_attack(DictAttack* instance) { + furi_assert(instance); + + with_view_model( + instance->view, DictAttackViewModel * model, { model->is_key_attack = false; }, true); +} diff --git a/nfc_magic/views/dict_attack.h b/nfc_magic/views/dict_attack.h new file mode 100644 index 00000000..54a0220f --- /dev/null +++ b/nfc_magic/views/dict_attack.h @@ -0,0 +1,50 @@ +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct DictAttack DictAttack; + +typedef enum { + DictAttackEventSkipPressed, +} DictAttackEvent; + +typedef void (*DictAttackCallback)(DictAttackEvent event, void* context); + +DictAttack* dict_attack_alloc(); + +void dict_attack_free(DictAttack* instance); + +void dict_attack_reset(DictAttack* instance); + +View* dict_attack_get_view(DictAttack* instance); + +void dict_attack_set_callback(DictAttack* instance, DictAttackCallback callback, void* context); + +void dict_attack_set_header(DictAttack* instance, const char* header); + +void dict_attack_set_card_state(DictAttack* instance, bool detected); + +void dict_attack_set_sectors_total(DictAttack* instance, uint8_t sectors_total); + +void dict_attack_set_sectors_read(DictAttack* instance, uint8_t sectors_read); + +void dict_attack_set_keys_found(DictAttack* instance, uint8_t keys_found); + +void dict_attack_set_current_sector(DictAttack* instance, uint8_t curr_sec); + +void dict_attack_set_total_dict_keys(DictAttack* instance, size_t dict_keys_total); + +void dict_attack_set_current_dict_key(DictAttack* instance, size_t cur_key_num); + +void dict_attack_set_key_attack(DictAttack* instance, uint8_t sector); + +void dict_attack_reset_key_attack(DictAttack* instance); + +#ifdef __cplusplus +} +#endif diff --git a/nfc_magic/views/write_problems.c b/nfc_magic/views/write_problems.c new file mode 100644 index 00000000..6b29c384 --- /dev/null +++ b/nfc_magic/views/write_problems.c @@ -0,0 +1,160 @@ +#include "write_problems.h" + +#include +#include "nfc_magic_icons.h" + +struct WriteProblems { + View* view; + WriteProblemsCallback callback; + void* context; +}; + +typedef struct { + uint8_t problem_index; + uint8_t problems_total; + FuriString* content; +} WriteProblemsViewModel; + +static void write_problems_view_draw_callback(Canvas* canvas, void* _model) { + WriteProblemsViewModel* model = _model; + FuriString* header = furi_string_alloc(); + canvas_clear(canvas); + + // Header + if(model->problems_total > 1) { + furi_string_printf( + header, "Warnings: %d of %d\n", model->problem_index + 1, model->problems_total); + } else { + furi_string_printf(header, "Warning!"); + } + + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned(canvas, 64, 0, AlignCenter, AlignTop, furi_string_get_cstr(header)); + + // Warning message + canvas_set_font(canvas, FontSecondary); + elements_text_box( + canvas, 1, 13, 76, 42, AlignLeft, AlignTop, furi_string_get_cstr(model->content), false); + + // Butttons + if(model->problem_index == model->problems_total - 1) { + elements_button_center(canvas, "Skip"); + elements_button_left(canvas, "Retry"); + } else { + elements_button_center(canvas, "Next"); + elements_button_left(canvas, "Back"); + } + + // Dolphin + canvas_draw_icon(canvas, 83, 22, &I_WarningDolphinFlip_45x42); + + furi_string_free(header); +} + +static bool write_problems_input_callback(InputEvent* event, void* context) { + WriteProblems* instance = context; + + if(event->type == InputTypeShort) { + if(event->key == InputKeyLeft) { + instance->callback(WriteProblemsEventLeftPressed, instance->context); + return true; + } else if(event->key == InputKeyOk) { + instance->callback(WriteProblemsEventCenterPressed, instance->context); + return true; + } + } + return false; +} + +WriteProblems* write_problems_alloc() { + WriteProblems* instance = malloc(sizeof(WriteProblems)); + instance->view = view_alloc(); + view_allocate_model(instance->view, ViewModelTypeLocking, sizeof(WriteProblemsViewModel)); + view_set_draw_callback(instance->view, write_problems_view_draw_callback); + view_set_input_callback(instance->view, write_problems_input_callback); + view_set_context(instance->view, instance); + with_view_model( + instance->view, + WriteProblemsViewModel * model, + { + model->content = furi_string_alloc(); + model->problem_index = 0; + model->problems_total = 0; + }, + false); + + return instance; +} + +void write_problems_free(WriteProblems* instance) { + furi_assert(instance); + + with_view_model( + instance->view, + WriteProblemsViewModel * model, + { furi_string_free(model->content); }, + false); + + view_free(instance->view); + free(instance); +} + +void write_problems_reset(WriteProblems* instance) { + furi_assert(instance); + + with_view_model( + instance->view, + WriteProblemsViewModel * model, + { + model->problem_index = 0; + model->problems_total = 0; + furi_string_reset(model->content); + }, + true); +} + +View* write_problems_get_view(WriteProblems* instance) { + furi_assert(instance); + + return instance->view; +} + +void write_problems_set_callback( + WriteProblems* instance, + WriteProblemsCallback callback, + void* context) { + furi_assert(instance); + instance->callback = callback; + instance->context = context; +} + +void write_problems_set_content(WriteProblems* instance, const char* content) { + furi_assert(instance); + furi_assert(content); + + with_view_model( + instance->view, + WriteProblemsViewModel * model, + { furi_string_set(model->content, content); }, + true); +} + +void write_problems_set_problems_total(WriteProblems* instance, uint8_t problems_total) { + furi_assert(instance); + + with_view_model( + instance->view, + WriteProblemsViewModel * model, + { model->problems_total = problems_total; }, + true); +} + +void write_problems_set_problem_index(WriteProblems* instance, uint8_t problem_index) { + furi_assert(instance); + + with_view_model( + instance->view, + WriteProblemsViewModel * model, + { model->problem_index = problem_index; }, + true); +} \ No newline at end of file diff --git a/nfc_magic/views/write_problems.h b/nfc_magic/views/write_problems.h new file mode 100644 index 00000000..a38b22f3 --- /dev/null +++ b/nfc_magic/views/write_problems.h @@ -0,0 +1,32 @@ +#pragma once + +#include +#include + +typedef struct WriteProblems WriteProblems; + +typedef enum { + WriteProblemsEventCenterPressed, + WriteProblemsEventLeftPressed, +} WriteProblemsEvent; + +typedef void (*WriteProblemsCallback)(WriteProblemsEvent event, void* context); + +WriteProblems* write_problems_alloc(); + +void write_problems_free(WriteProblems* instance); + +void write_problems_reset(WriteProblems* instance); + +View* write_problems_get_view(WriteProblems* instance); + +void write_problems_set_callback( + WriteProblems* instance, + WriteProblemsCallback callback, + void* context); + +void write_problems_set_content(WriteProblems* instance, const char* content); + +void write_problems_set_problem_index(WriteProblems* instance, uint8_t index); + +void write_problems_set_problems_total(WriteProblems* instance, uint8_t total); From cb15cbd202f2a1dbb0fd86e51fa051329cd52328 Mon Sep 17 00:00:00 2001 From: Nathan N Date: Thu, 4 Apr 2024 03:36:11 -0400 Subject: [PATCH 2/5] Replace Mfkey32 with MFKey (#189) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく Co-authored-by: hedger Co-authored-by: hedger --- .github/workflows/build.yml | 18 +- mfkey/.catalog/README.md | 12 + mfkey/.catalog/changelog.md | 6 + mfkey/.catalog/screenshots/1.png | Bin 0 -> 4277 bytes mfkey/.catalog/screenshots/2.png | Bin 0 -> 6980 bytes mfkey/.catalog/screenshots/3.png | Bin 0 -> 5937 bytes {mfkey32 => mfkey}/application.fam | 18 +- mfkey/crypto1.c | 22 + mfkey/crypto1.h | 156 ++++ mfkey/images/mfkey.png | Bin 0 -> 107 bytes mfkey/init_plugin.c | 412 +++++++++ mfkey/mfkey.c | 898 ++++++++++++++++++ mfkey/mfkey.h | 107 +++ mfkey/mfkey.png | Bin 0 -> 107 bytes mfkey/plugin_interface.h | 13 + mfkey32/.catalog/README.md | 12 - mfkey32/.catalog/changelog.md | 4 - mfkey32/.catalog/screenshots/1.png | Bin 2077 -> 0 bytes mfkey32/.catalog/screenshots/2.png | Bin 1610 -> 0 bytes mfkey32/.catalog/screenshots/3.png | Bin 2162 -> 0 bytes mfkey32/images/mfkey.png | Bin 9060 -> 0 bytes mfkey32/mfkey.png | Bin 9060 -> 0 bytes mfkey32/mfkey32.c | 1354 ---------------------------- 23 files changed, 1652 insertions(+), 1380 deletions(-) create mode 100644 mfkey/.catalog/README.md create mode 100644 mfkey/.catalog/changelog.md create mode 100644 mfkey/.catalog/screenshots/1.png create mode 100644 mfkey/.catalog/screenshots/2.png create mode 100644 mfkey/.catalog/screenshots/3.png rename {mfkey32 => mfkey}/application.fam (50%) create mode 100644 mfkey/crypto1.c create mode 100644 mfkey/crypto1.h create mode 100644 mfkey/images/mfkey.png create mode 100644 mfkey/init_plugin.c create mode 100644 mfkey/mfkey.c create mode 100644 mfkey/mfkey.h create mode 100644 mfkey/mfkey.png create mode 100644 mfkey/plugin_interface.h delete mode 100644 mfkey32/.catalog/README.md delete mode 100644 mfkey32/.catalog/changelog.md delete mode 100644 mfkey32/.catalog/screenshots/1.png delete mode 100644 mfkey32/.catalog/screenshots/2.png delete mode 100644 mfkey32/.catalog/screenshots/3.png delete mode 100644 mfkey32/images/mfkey.png delete mode 100644 mfkey32/mfkey.png delete mode 100644 mfkey32/mfkey32.c diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d3b6327d..4dfb8981 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -21,14 +21,14 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 2 submodules: recursive - name: Set up ufbt - uses: flipperdevices/flipperzero-ufbt-action@v0.1.2 + uses: flipperdevices/flipperzero-ufbt-action@v0.1 with: sdk-channel: ${{ github.event.inputs.sdk-channel || 'rc'}} task: setup @@ -48,8 +48,12 @@ jobs: run: | BUILD_FAILED=0 for appdir in ${{ steps.changed-files.outputs.all_changed_and_modified_files }} ; do + if [ ! -f "$appdir/application.fam" ] ; then + echo "Skipping $appdir, no application.fam. File may have been deleted." + continue + fi echo "Building in $appdir" - pushd $appdir + pushd "$appdir" if ! ufbt build faps ; then echo "::error::Failed to build $appdir" BUILD_FAILED=1 @@ -66,8 +70,12 @@ jobs: run: | BUILD_FAILED=0 for appdir in $( dirname $( find . -name application.fam ) ) ; do + if [ ! -f "$appdir/application.fam" ] ; then + echo "Skipping $appdir, no application.fam. File may have been deleted." + continue + fi echo "Building in $appdir" - pushd $appdir + pushd "$appdir" if ! ufbt build faps ; then BUILD_FAILED=1 echo "::error::Failed to build $appdir" @@ -85,7 +93,7 @@ jobs: cp -v $( find . -name '*.fap' ) dist/ - name: Upload all .fap files - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: faps path: dist/*.fap diff --git a/mfkey/.catalog/README.md b/mfkey/.catalog/README.md new file mode 100644 index 00000000..7cf89be0 --- /dev/null +++ b/mfkey/.catalog/README.md @@ -0,0 +1,12 @@ +# Flipper Zero MFKey + +This application allows you to calculate the keys of MIFARE Classic cards using the Mfkey32 and Nested algorithms directly on your Flipper Zero. After collecting the nonces using the Detect Reader feature of the NFC app, they can be used to calculate the keys to the card in the MFKey app. + +## Usage + +After collecting nonces using the Detect Reader option, press the Start button in the MFKey app and wait for it to finish. The calculation can take more than 10 minutes, so you'll have to be patient. After the calculation is complete, the keys will be saved to the user key dictionary. + +## Credits + +Developers: noproto, AG +Thanks: bettse diff --git a/mfkey/.catalog/changelog.md b/mfkey/.catalog/changelog.md new file mode 100644 index 00000000..db254bc0 --- /dev/null +++ b/mfkey/.catalog/changelog.md @@ -0,0 +1,6 @@ +## 2.0 + - Added Nested key recovery, use new KeysDict API, fix crashes, more efficient RAM utilization, faster +## 1.1 + - Rework application with new NFC API +## 1.0 + - Initial release diff --git a/mfkey/.catalog/screenshots/1.png b/mfkey/.catalog/screenshots/1.png new file mode 100644 index 0000000000000000000000000000000000000000..c3cc6886773d50587294fc05a8afa563e0458fec GIT binary patch literal 4277 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 literal 0 HcmV?d00001 diff --git a/mfkey/.catalog/screenshots/2.png b/mfkey/.catalog/screenshots/2.png new file mode 100644 index 0000000000000000000000000000000000000000..9fc4407e59b9325ddd61586244ac152706ec1917 GIT binary patch literal 6980 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ǯ?neT@~YA$GQ|E+iSE{IhH6FCgTN$@4PJ znyux-9IK-Od&6$qM`ZQ;XGpg^|khDai3>OI-B<3Y6F&B_+n*kT$}u<-=88`fS^} z!}~RlNSc>My0U#cKaeGWX;a4iaCKev2gW)kYr@uWF9>SK7@FCQ zp?RcuCMuMyX4cJ8H41bvH0ERms6aMuH%V&Azl&klq+Aye$Y`SlW%nG)MS_W>5s2q) z)*!E}>tizzU|Lg|%)GeKWeJGcGJA(}M|Uo>sZF>HOuSX@R*%W#(6B(IiiCOnaGkWD zU~*;K{98wNTrXwWB6(DWTiI2R-{4!m?30okZ{BLMb~$#bGb`5Yv8-ejpd4QW>4$C; z+V8EtD;)^_L%CPCr~*^o8SZ@H^>e|4EdH=dil8OY+4vVK8f0Ns0PheqdlSDs*}#mO zmh2bl{y_;CK93mjOMV`L82sSxuG;5@0yxH9a14&D%nxyC?Z^ttIq+S@SD?vCyoO=NnMz{s2#c^NY%|L+m#y5ZjG9JnOUn()Ug8 zEF%q}DgeUr22VMSzkaDz{pt>FwD|Dt0zncC`vGmr!2DJRN5(BM(oFn=yEu)XV`)#; z_7cd(k0DK4a04nJAgg=OHgn5-X(d>tJySVBu}=g?q!rHi6dcJGF(*=9+`F)>3B>Am zqHoZRTzMYVRvw`*2F0q_4SZ%b>y@`}{kRYGa>}0h*KvZhd0mR=NZ(xD&x0PH_%Ee^ zkdH<2vK6nq8G<8c&&jz#wXFl$G>%~S`FFTrxzAZB7Tq?c3_B^r@LKg2<;A_USzQ{s zc9ITPu~2M|^FvT?fPWy>>%z+#Em3&82XR} z{%7p1r{-@;Jr)>Cn9kwkmSO3Y5^mt`K5a?x<|Vh*C;k-pwv5I(*AStM%(>pPM@18r zBmg0sC_xe0 zlshBy0wwTAu0l!pwXtseg|Ft~v0Uryaec%h>Mw$y`VCF(hymow-2$KC{p*6pnqRC`t+L z@mofGO>UE$U-0M;nf39t){0v=Q%jlWcA|yn*Y;cr7p1SkmEIub&63haW-^bilStRg z?1HxDr>dBz+tH2;%k8F!Q;4#|N$8fI*WD zkPwrQ5)qRWkq|c)mscQ*MG{g%VqywnVlJ?`n*T*`^SJBc82FzAIRz={e+bE8l5jFX p;D0LkxVWLceeSq<{s%`|OhHoiA5LsS;tZJsbTm-vW!LQ<{}(gqvjhMD literal 0 HcmV?d00001 diff --git a/mfkey/.catalog/screenshots/3.png b/mfkey/.catalog/screenshots/3.png new file mode 100644 index 0000000000000000000000000000000000000000..7eebc6377c82fd002f60f96077578c0f4307f94c GIT binary patch literal 5937 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 X{6C#c`qnxSI6zy&K)p)UKIT6F&zg|h literal 0 HcmV?d00001 diff --git a/mfkey32/application.fam b/mfkey/application.fam similarity index 50% rename from mfkey32/application.fam rename to mfkey/application.fam index bb2b2452..23bd9a4a 100644 --- a/mfkey32/application.fam +++ b/mfkey/application.fam @@ -1,19 +1,27 @@ App( - appid="mfkey32", - name="Mfkey32", + appid="mfkey", + name="MFKey", apptype=FlipperAppType.EXTERNAL, targets=["f7"], - entry_point="mfkey32_main", + entry_point="mfkey_main", requires=[ "gui", "storage", ], stack_size=1 * 1024, - fap_description="Mf Classic key finder", - fap_version="1.1", fap_icon="mfkey.png", fap_category="NFC", fap_author="@noproto", fap_icon_assets="images", fap_weburl="https://github.com/noproto/FlipperMfkey", + fap_description="MIFARE Classic key recovery tool", + fap_version="2.2", +) + +App( + appid="mfkey_init_plugin", + apptype=FlipperAppType.PLUGIN, + entry_point="init_plugin_ep", + requires=["mfkey"], + sources=["init_plugin.c"], ) diff --git a/mfkey/crypto1.c b/mfkey/crypto1.c new file mode 100644 index 00000000..e8734500 --- /dev/null +++ b/mfkey/crypto1.c @@ -0,0 +1,22 @@ +#pragma GCC optimize("O3") +#pragma GCC optimize("-funroll-all-loops") + +#include +#include "crypto1.h" +#include "mfkey.h" + +#define BIT(x, n) ((x) >> (n) & 1) + +void crypto1_get_lfsr(struct Crypto1State* state, MfClassicKey* lfsr) { + int i; + uint64_t lfsr_value = 0; + for(i = 23; i >= 0; --i) { + lfsr_value = lfsr_value << 1 | BIT(state->odd, i ^ 3); + lfsr_value = lfsr_value << 1 | BIT(state->even, i ^ 3); + } + + // Assign the key value to the MfClassicKey struct + for(i = 0; i < 6; ++i) { + lfsr->data[i] = (lfsr_value >> ((5 - i) * 8)) & 0xFF; + } +} \ No newline at end of file diff --git a/mfkey/crypto1.h b/mfkey/crypto1.h new file mode 100644 index 00000000..7a68d713 --- /dev/null +++ b/mfkey/crypto1.h @@ -0,0 +1,156 @@ +#ifndef CRYPTO1_H +#define CRYPTO1_H + +#include +#include "mfkey.h" +#include + +#define LF_POLY_ODD (0x29CE5C) +#define LF_POLY_EVEN (0x870804) +#define BIT(x, n) ((x) >> (n) & 1) +#define BEBIT(x, n) BIT(x, (n) ^ 24) +#define SWAPENDIAN(x) \ + ((x) = ((x) >> 8 & 0xff00ff) | ((x) & 0xff00ff) << 8, (x) = (x) >> 16 | (x) << 16) + +static inline uint32_t prng_successor(uint32_t x, uint32_t n); +static inline int filter(uint32_t const x); +static inline uint8_t evenparity32(uint32_t x); +static inline void update_contribution(unsigned int data[], int item, int mask1, int mask2); +void crypto1_get_lfsr(struct Crypto1State* state, MfClassicKey* lfsr); +static inline uint32_t crypt_word(struct Crypto1State* s); +static inline void crypt_word_noret(struct Crypto1State* s, uint32_t in, int x); +static inline uint32_t crypt_word_ret(struct Crypto1State* s, uint32_t in, int x); +static inline void rollback_word_noret(struct Crypto1State* s, uint32_t in, int x); + +static const uint8_t lookup1[256] = { + 0, 0, 16, 16, 0, 16, 0, 0, 0, 16, 0, 0, 16, 16, 16, 16, 0, 0, 16, 16, 0, 16, 0, 0, + 0, 16, 0, 0, 16, 16, 16, 16, 0, 0, 16, 16, 0, 16, 0, 0, 0, 16, 0, 0, 16, 16, 16, 16, + 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, 8, 8, 24, 24, 8, 24, 8, 8, + 8, 24, 8, 8, 24, 24, 24, 24, 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, + 0, 0, 16, 16, 0, 16, 0, 0, 0, 16, 0, 0, 16, 16, 16, 16, 0, 0, 16, 16, 0, 16, 0, 0, + 0, 16, 0, 0, 16, 16, 16, 16, 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, + 0, 0, 16, 16, 0, 16, 0, 0, 0, 16, 0, 0, 16, 16, 16, 16, 0, 0, 16, 16, 0, 16, 0, 0, + 0, 16, 0, 0, 16, 16, 16, 16, 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, + 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, 0, 0, 16, 16, 0, 16, 0, 0, + 0, 16, 0, 0, 16, 16, 16, 16, 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, + 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24}; +static const uint8_t lookup2[256] = { + 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, + 4, 4, 4, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, + 2, 2, 6, 6, 6, 6, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 2, 2, 6, 6, 2, 6, 2, + 2, 2, 6, 2, 2, 6, 6, 6, 6, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 0, 0, 4, 4, + 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 2, + 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, + 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, + 2, 6, 6, 6, 6, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6, 2, 2, 6, 6, 2, 6, 2, 2, + 2, 6, 2, 2, 6, 6, 6, 6, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6}; + +static inline int filter(uint32_t const x) { + uint32_t f; + f = lookup1[x & 0xff] | lookup2[(x >> 8) & 0xff]; + f |= 0x0d938 >> (x >> 16 & 0xf) & 1; + return BIT(0xEC57E80A, f); +} + +#ifndef __ARM_ARCH_7EM__ +static inline uint8_t evenparity32(uint32_t x) { + return __builtin_parity(x); +} +#endif + +#ifdef __ARM_ARCH_7EM__ +static inline uint8_t evenparity32(uint32_t x) { + uint32_t result; + __asm__ volatile("eor r1, %[x], %[x], lsr #16 \n\t" // r1 = x ^ (x >> 16) + "eor r1, r1, r1, lsr #8 \n\t" // r1 = r1 ^ (r1 >> 8) + "eor r1, r1, r1, lsr #4 \n\t" // r1 = r1 ^ (r1 >> 4) + "eor r1, r1, r1, lsr #2 \n\t" // r1 = r1 ^ (r1 >> 2) + "eor r1, r1, r1, lsr #1 \n\t" // r1 = r1 ^ (r1 >> 1) + "and %[result], r1, #1 \n\t" // result = r1 & 1 + : [result] "=r"(result) + : [x] "r"(x) + : "r1"); + return result; +} +#endif + +static inline void update_contribution(unsigned int data[], int item, int mask1, int mask2) { + int p = data[item] >> 25; + p = p << 1 | evenparity32(data[item] & mask1); + p = p << 1 | evenparity32(data[item] & mask2); + data[item] = p << 24 | (data[item] & 0xffffff); +} + +static inline uint32_t crypt_word(struct Crypto1State* s) { + // "in" and "x" are always 0 (last iteration) + uint32_t res_ret = 0; + uint32_t feedin, t; + for(int i = 0; i <= 31; i++) { + res_ret |= (filter(s->odd) << (24 ^ i)); //-V629 + feedin = LF_POLY_EVEN & s->even; + feedin ^= LF_POLY_ODD & s->odd; + s->even = s->even << 1 | (evenparity32(feedin)); + t = s->odd, s->odd = s->even, s->even = t; + } + return res_ret; +} + +static inline void crypt_word_noret(struct Crypto1State* s, uint32_t in, int x) { + uint8_t ret; + uint32_t feedin, t, next_in; + for(int i = 0; i <= 31; i++) { + next_in = BEBIT(in, i); + ret = filter(s->odd); + feedin = ret & (!!x); + feedin ^= LF_POLY_EVEN & s->even; + feedin ^= LF_POLY_ODD & s->odd; + feedin ^= !!next_in; + s->even = s->even << 1 | (evenparity32(feedin)); + t = s->odd, s->odd = s->even, s->even = t; + } + return; +} + +static inline uint32_t crypt_word_ret(struct Crypto1State* s, uint32_t in, int x) { + uint32_t ret = 0; + uint32_t feedin, t, next_in; + uint8_t next_ret; + for(int i = 0; i <= 31; i++) { + next_in = BEBIT(in, i); + next_ret = filter(s->odd); + feedin = next_ret & (!!x); + feedin ^= LF_POLY_EVEN & s->even; + feedin ^= LF_POLY_ODD & s->odd; + feedin ^= !!next_in; + s->even = s->even << 1 | (evenparity32(feedin)); + t = s->odd, s->odd = s->even, s->even = t; + ret |= next_ret << (24 ^ i); + } + return ret; +} + +static inline void rollback_word_noret(struct Crypto1State* s, uint32_t in, int x) { + uint8_t ret; + uint32_t feedin, t, next_in; + for(int i = 31; i >= 0; i--) { + next_in = BEBIT(in, i); + s->odd &= 0xffffff; + t = s->odd, s->odd = s->even, s->even = t; + ret = filter(s->odd); + feedin = ret & (!!x); + feedin ^= s->even & 1; + feedin ^= LF_POLY_EVEN & (s->even >>= 1); + feedin ^= LF_POLY_ODD & s->odd; + feedin ^= !!next_in; + s->even |= (evenparity32(feedin)) << 23; + } + return; +} + +static inline uint32_t prng_successor(uint32_t x, uint32_t n) { + SWAPENDIAN(x); + while(n--) x = x >> 1 | (x >> 16 ^ x >> 18 ^ x >> 19 ^ x >> 21) << 31; + return SWAPENDIAN(x); +} + +#endif // CRYPTO1_H \ No newline at end of file diff --git a/mfkey/images/mfkey.png b/mfkey/images/mfkey.png new file mode 100644 index 0000000000000000000000000000000000000000..f1694bb335a8619e53cd3b98651ba995cc7a1caf GIT binary patch literal 107 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2xGmzZ=C-xtZVhivIasB`QKad%E=yDy9Qt)(f z4B@z*{KD=)L3iUrapuFX*xK~i1d{R-5*QT~m>F7Tu$Q|1zuOE{%i!ti=d#Wzp$P!I C{~rec literal 0 HcmV?d00001 diff --git a/mfkey/init_plugin.c b/mfkey/init_plugin.c new file mode 100644 index 00000000..3f18c135 --- /dev/null +++ b/mfkey/init_plugin.c @@ -0,0 +1,412 @@ +#include +#include +#include +#include +#include +#include +#include "mfkey.h" +#include "crypto1.h" +#include "plugin_interface.h" +#include + +// TODO: Remove defines that are not needed +#define KEYS_DICT_SYSTEM_PATH EXT_PATH("nfc/assets/mf_classic_dict.nfc") +#define KEYS_DICT_USER_PATH EXT_PATH("nfc/assets/mf_classic_dict_user.nfc") +#define MF_CLASSIC_NONCE_PATH EXT_PATH("nfc/.mfkey32.log") +#define MF_CLASSIC_NESTED_NONCE_PATH EXT_PATH("nfc/.nested") +#define TAG "MFKey" +#define MAX_NAME_LEN 32 +#define MAX_PATH_LEN 64 + +#define LF_POLY_ODD (0x29CE5C) +#define LF_POLY_EVEN (0x870804) +#define CONST_M1_1 (LF_POLY_EVEN << 1 | 1) +#define CONST_M2_1 (LF_POLY_ODD << 1) +#define CONST_M1_2 (LF_POLY_ODD) +#define CONST_M2_2 (LF_POLY_EVEN << 1 | 1) +#define BIT(x, n) ((x) >> (n) & 1) +#define BEBIT(x, n) BIT(x, (n) ^ 24) +#define SWAPENDIAN(x) \ + ((x) = ((x) >> 8 & 0xff00ff) | ((x) & 0xff00ff) << 8, (x) = (x) >> 16 | (x) << 16) + +bool key_already_found_for_nonce_in_dict(KeysDict* dict, MfClassicNonce* nonce) { + bool found = false; + uint8_t key_bytes[sizeof(MfClassicKey)]; + keys_dict_rewind(dict); + while(keys_dict_get_next_key(dict, key_bytes, sizeof(MfClassicKey))) { + uint64_t k = bit_lib_bytes_to_num_be(key_bytes, sizeof(MfClassicKey)); + struct Crypto1State temp = {0, 0}; + for(int i = 0; i < 24; i++) { + (&temp)->odd |= (BIT(k, 2 * i + 1) << (i ^ 3)); + (&temp)->even |= (BIT(k, 2 * i) << (i ^ 3)); + } + if(nonce->attack == mfkey32) { + crypt_word_noret(&temp, nonce->uid_xor_nt1, 0); + crypt_word_noret(&temp, nonce->nr1_enc, 1); + if(nonce->ar1_enc == (crypt_word(&temp) ^ nonce->p64b)) { + found = true; + break; + } + } else if(nonce->attack == static_nested) { + uint32_t expected_ks1 = crypt_word_ret(&temp, nonce->uid_xor_nt0, 0); + if(nonce->ks1_1_enc == expected_ks1) { + found = true; + break; + } + } + } + return found; +} + +bool napi_mf_classic_mfkey32_nonces_check_presence() { + Storage* storage = furi_record_open(RECORD_STORAGE); + + bool nonces_present = storage_common_stat(storage, MF_CLASSIC_NONCE_PATH, NULL) == FSE_OK; + + furi_record_close(RECORD_STORAGE); + + return nonces_present; +} + +bool distance_in_nonces_file(const char* file_path, const char* file_name) { + char full_path[MAX_PATH_LEN]; + snprintf(full_path, sizeof(full_path), "%s/%s", file_path, file_name); + bool distance_present = false; + Storage* storage = furi_record_open(RECORD_STORAGE); + Stream* file_stream = buffered_file_stream_alloc(storage); + FuriString* line_str; + line_str = furi_string_alloc(); + + if(buffered_file_stream_open(file_stream, full_path, FSAM_READ, FSOM_OPEN_EXISTING)) { + while(true) { + if(!stream_read_line(file_stream, line_str)) break; + if(furi_string_search_str(line_str, "distance") != FURI_STRING_FAILURE) { + distance_present = true; + break; + } + } + } + + buffered_file_stream_close(file_stream); + stream_free(file_stream); + furi_string_free(line_str); + furi_record_close(RECORD_STORAGE); + + return distance_present; +} + +bool napi_mf_classic_nested_nonces_check_presence() { + Storage* storage = furi_record_open(RECORD_STORAGE); + + if(!(storage_dir_exists(storage, MF_CLASSIC_NESTED_NONCE_PATH))) { + furi_record_close(RECORD_STORAGE); + return false; + } + + bool nonces_present = false; + File* dir = storage_file_alloc(storage); + char filename_buffer[MAX_NAME_LEN]; + FileInfo file_info; + + if(storage_dir_open(dir, MF_CLASSIC_NESTED_NONCE_PATH)) { + while(storage_dir_read(dir, &file_info, filename_buffer, MAX_NAME_LEN)) { + // We only care about Static Nested files + if(!(file_info.flags & FSF_DIRECTORY) && strstr(filename_buffer, ".nonces") && + !(distance_in_nonces_file(MF_CLASSIC_NESTED_NONCE_PATH, filename_buffer))) { + nonces_present = true; + break; + } + } + } + + storage_dir_close(dir); + storage_file_free(dir); + furi_record_close(RECORD_STORAGE); + + return nonces_present; +} + +int binaryStringToInt(const char* binStr) { + int result = 0; + while(*binStr) { + result <<= 1; + if(*binStr == '1') { + result |= 1; + } + binStr++; + } + return result; +} + +bool load_mfkey32_nonces( + MfClassicNonceArray* nonce_array, + ProgramState* program_state, + KeysDict* system_dict, + bool system_dict_exists, + KeysDict* user_dict) { + bool array_loaded = false; + + do { + // https://github.com/flipperdevices/flipperzero-firmware/blob/5134f44c09d39344a8747655c0d59864bb574b96/applications/services/storage/filesystem_api_defines.h#L8-L22 + if(!buffered_file_stream_open( + nonce_array->stream, MF_CLASSIC_NONCE_PATH, FSAM_READ_WRITE, FSOM_OPEN_EXISTING)) { + buffered_file_stream_close(nonce_array->stream); + break; + } + + // Check for newline ending + if(!stream_eof(nonce_array->stream)) { + if(!stream_seek(nonce_array->stream, -1, StreamOffsetFromEnd)) break; + uint8_t last_char = 0; + if(stream_read(nonce_array->stream, &last_char, 1) != 1) break; + if(last_char != '\n') { + //FURI_LOG_D(TAG, "Adding new line ending"); + if(stream_write_char(nonce_array->stream, '\n') != 1) break; + } + if(!stream_rewind(nonce_array->stream)) break; + } + + // Read total amount of nonces + FuriString* next_line; + next_line = furi_string_alloc(); + while(!(program_state->close_thread_please)) { + if(!stream_read_line(nonce_array->stream, next_line)) { + //FURI_LOG_T(TAG, "No nonces left"); + break; + } + /* + FURI_LOG_T( + TAG, + "Read line: %s, len: %zu", + furi_string_get_cstr(next_line), + furi_string_size(next_line)); + */ + if(!furi_string_start_with_str(next_line, "Sec")) continue; + const char* next_line_cstr = furi_string_get_cstr(next_line); + MfClassicNonce res = {0}; + res.attack = mfkey32; + int i = 0; + char* endptr; + for(i = 0; i <= 17; i++) { + if(i != 0) { + next_line_cstr = strchr(next_line_cstr, ' '); + if(next_line_cstr) { + next_line_cstr++; + } else { + break; + } + } + unsigned long value = strtoul(next_line_cstr, &endptr, 16); + switch(i) { + case 5: + res.uid = value; + break; + case 7: + res.nt0 = value; + break; + case 9: + res.nr0_enc = value; + break; + case 11: + res.ar0_enc = value; + break; + case 13: + res.nt1 = value; + break; + case 15: + res.nr1_enc = value; + break; + case 17: + res.ar1_enc = value; + break; + default: + break; // Do nothing + } + next_line_cstr = endptr; + } + res.p64 = prng_successor(res.nt0, 64); + res.p64b = prng_successor(res.nt1, 64); + res.uid_xor_nt0 = res.uid ^ res.nt0; + res.uid_xor_nt1 = res.uid ^ res.nt1; + + (program_state->total)++; + if((system_dict_exists && key_already_found_for_nonce_in_dict(system_dict, &res)) || + (key_already_found_for_nonce_in_dict(user_dict, &res))) { + (program_state->cracked)++; + (program_state->num_completed)++; + continue; + } + //FURI_LOG_I(TAG, "No key found for %8lx %8lx", res.uid, res.ar1_enc); + // TODO: Refactor + nonce_array->remaining_nonce_array = realloc( //-V701 + nonce_array->remaining_nonce_array, + sizeof(MfClassicNonce) * ((nonce_array->remaining_nonces) + 1)); + nonce_array->remaining_nonces++; + nonce_array->remaining_nonce_array[(nonce_array->remaining_nonces) - 1] = res; + nonce_array->total_nonces++; + } + furi_string_free(next_line); + buffered_file_stream_close(nonce_array->stream); + //stream_free(nonce_array->stream); + + array_loaded = true; + //FURI_LOG_I(TAG, "Loaded %lu Mfkey32 nonces", nonce_array->total_nonces); + } while(false); + + return array_loaded; +} + +bool load_nested_nonces( + MfClassicNonceArray* nonce_array, + ProgramState* program_state, + KeysDict* system_dict, + bool system_dict_exists, + KeysDict* user_dict) { + Storage* storage = furi_record_open(RECORD_STORAGE); + File* dir = storage_file_alloc(storage); + char filename_buffer[MAX_NAME_LEN]; + FileInfo file_info; + FuriString* next_line = furi_string_alloc(); + + if(!storage_dir_open(dir, MF_CLASSIC_NESTED_NONCE_PATH)) { + storage_dir_close(dir); + storage_file_free(dir); + furi_record_close(RECORD_STORAGE); + furi_string_free(next_line); + return false; + } + + while(storage_dir_read(dir, &file_info, filename_buffer, MAX_NAME_LEN)) { + if(!(file_info.flags & FSF_DIRECTORY) && strstr(filename_buffer, ".nonces") && + !(distance_in_nonces_file(MF_CLASSIC_NESTED_NONCE_PATH, filename_buffer))) { + char full_path[MAX_PATH_LEN]; + snprintf( + full_path, + sizeof(full_path), + "%s/%s", + MF_CLASSIC_NESTED_NONCE_PATH, + filename_buffer); + + // TODO: We should only need READ_WRITE here if we plan on adding a newline to the end of the file if has none + if(!buffered_file_stream_open( + nonce_array->stream, full_path, FSAM_READ_WRITE, FSOM_OPEN_EXISTING)) { + buffered_file_stream_close(nonce_array->stream); + continue; + } + + while(stream_read_line(nonce_array->stream, next_line)) { + if(furi_string_search_str(next_line, "Nested:") != FURI_STRING_FAILURE) { + MfClassicNonce res = {0}; + res.attack = static_nested; + int parsed = sscanf( + furi_string_get_cstr(next_line), + "Nested: %*s %*s cuid 0x%" PRIx32 " nt0 0x%" PRIx32 " ks0 0x%" PRIx32 + " par0 %4[01] nt1 0x%" PRIx32 " ks1 0x%" PRIx32 " par1 %4[01]", + &res.uid, + &res.nt0, + &res.ks1_1_enc, + res.par_1_str, + &res.nt1, + &res.ks1_2_enc, + res.par_2_str); + + if(parsed != 7) continue; + res.par_1 = binaryStringToInt(res.par_1_str); + res.par_2 = binaryStringToInt(res.par_2_str); + res.uid_xor_nt0 = res.uid ^ res.nt0; + res.uid_xor_nt1 = res.uid ^ res.nt1; + + (program_state->total)++; + if((system_dict_exists && + key_already_found_for_nonce_in_dict(system_dict, &res)) || + (key_already_found_for_nonce_in_dict(user_dict, &res))) { + (program_state->cracked)++; + (program_state->num_completed)++; + continue; + } + + nonce_array->remaining_nonce_array = realloc( + nonce_array->remaining_nonce_array, + sizeof(MfClassicNonce) * (nonce_array->remaining_nonces + 1)); + nonce_array->remaining_nonce_array[nonce_array->remaining_nonces] = res; + nonce_array->remaining_nonces++; + nonce_array->total_nonces++; + } + } + + buffered_file_stream_close(nonce_array->stream); + } + } + + storage_dir_close(dir); + storage_file_free(dir); + furi_record_close(RECORD_STORAGE); + furi_string_free(next_line); + + //FURI_LOG_I(TAG, "Loaded %lu Static Nested nonces", nonce_array->total_nonces); + return true; +} + +MfClassicNonceArray* napi_mf_classic_nonce_array_alloc( + KeysDict* system_dict, + bool system_dict_exists, + KeysDict* user_dict, + ProgramState* program_state) { + MfClassicNonceArray* nonce_array = malloc(sizeof(MfClassicNonceArray)); + MfClassicNonce* remaining_nonce_array_init = malloc(sizeof(MfClassicNonce) * 1); + nonce_array->remaining_nonce_array = remaining_nonce_array_init; + Storage* storage = furi_record_open(RECORD_STORAGE); + nonce_array->stream = buffered_file_stream_alloc(storage); + furi_record_close(RECORD_STORAGE); + + bool array_loaded = false; + + if(program_state->mfkey32_present) { + array_loaded = load_mfkey32_nonces( + nonce_array, program_state, system_dict, system_dict_exists, user_dict); + } + + if(program_state->nested_present) { + array_loaded |= load_nested_nonces( + nonce_array, program_state, system_dict, system_dict_exists, user_dict); + } + + if(!array_loaded) { + free(nonce_array); + nonce_array = NULL; + } + + return nonce_array; +} + +void napi_mf_classic_nonce_array_free(MfClassicNonceArray* nonce_array) { + // TODO: Track free state at the time this is called to ensure double free does not happen + furi_assert(nonce_array); + furi_assert(nonce_array->stream); + + buffered_file_stream_close(nonce_array->stream); + stream_free(nonce_array->stream); + free(nonce_array); +} + +/* Actual implementation of app<>plugin interface */ +static const MfkeyPlugin init_plugin = { + .name = "Initialization Plugin", + .napi_mf_classic_mfkey32_nonces_check_presence = + &napi_mf_classic_mfkey32_nonces_check_presence, + .napi_mf_classic_nested_nonces_check_presence = &napi_mf_classic_nested_nonces_check_presence, + .napi_mf_classic_nonce_array_alloc = &napi_mf_classic_nonce_array_alloc, + .napi_mf_classic_nonce_array_free = &napi_mf_classic_nonce_array_free, +}; + +/* Plugin descriptor to comply with basic plugin specification */ +static const FlipperAppPluginDescriptor init_plugin_descriptor = { + .appid = PLUGIN_APP_ID, + .ep_api_version = PLUGIN_API_VERSION, + .entry_point = &init_plugin, +}; + +/* Plugin entry point - must return a pointer to const descriptor */ +const FlipperAppPluginDescriptor* init_plugin_ep() { + return &init_plugin_descriptor; +} \ No newline at end of file diff --git a/mfkey/mfkey.c b/mfkey/mfkey.c new file mode 100644 index 00000000..055c7cea --- /dev/null +++ b/mfkey/mfkey.c @@ -0,0 +1,898 @@ +#pragma GCC optimize("O3") +#pragma GCC optimize("-funroll-all-loops") + +// TODO: Add keys to top of the user dictionary, not the bottom +// TODO: More efficient dictionary bruteforce by scanning through hardcoded very common keys and previously found dictionary keys first? +// (a cache for key_already_found_for_nonce_in_dict) +// TODO: Selectively unroll loops to reduce binary size +// TODO: Collect parity during Mfkey32 attacks to further optimize the attack +// TODO: Why different sscanf between Mfkey32 and Nested? +// TODO: "Read tag again with NFC app" message upon completion, "Complete. Keys added: " +// TODO: Separate Mfkey32 and Nested functions where possible to reduce branch statements +// TODO: More accurate timing for Nested +// TODO: Find ~1 KB memory leak + +#include +#include +#include +#include +#include "mfkey_icons.h" +#include +#include +#include +#include +#include +#include +#include +#include "mfkey.h" +#include "crypto1.h" +#include "plugin_interface.h" +#include +#include +#include + +// TODO: Remove defines that are not needed +#define KEYS_DICT_SYSTEM_PATH EXT_PATH("nfc/assets/mf_classic_dict.nfc") +#define KEYS_DICT_USER_PATH EXT_PATH("nfc/assets/mf_classic_dict_user.nfc") +#define MF_CLASSIC_NONCE_PATH EXT_PATH("nfc/.mfkey32.log") +#define MF_CLASSIC_NESTED_NONCE_PATH EXT_PATH("nfc/.nested") +#define TAG "MFKey" +#define MAX_NAME_LEN 32 +#define MAX_PATH_LEN 64 + +#define LF_POLY_ODD (0x29CE5C) +#define LF_POLY_EVEN (0x870804) +#define CONST_M1_1 (LF_POLY_EVEN << 1 | 1) +#define CONST_M2_1 (LF_POLY_ODD << 1) +#define CONST_M1_2 (LF_POLY_ODD) +#define CONST_M2_2 (LF_POLY_EVEN << 1 | 1) +#define BIT(x, n) ((x) >> (n) & 1) +#define BEBIT(x, n) BIT(x, (n) ^ 24) +#define SWAPENDIAN(x) \ + ((x) = ((x) >> 8 & 0xff00ff) | ((x) & 0xff00ff) << 8, (x) = (x) >> 16 | (x) << 16) +//#define SIZEOF(arr) sizeof(arr) / sizeof(*arr) + +static int eta_round_time = 56; +static int eta_total_time = 900; +// MSB_LIMIT: Chunk size (out of 256) +static int MSB_LIMIT = 16; + +int check_state(struct Crypto1State* t, MfClassicNonce* n) { + if(!(t->odd | t->even)) return 0; + if(n->attack == mfkey32) { + rollback_word_noret(t, 0, 0); + rollback_word_noret(t, n->nr0_enc, 1); + rollback_word_noret(t, n->uid_xor_nt0, 0); + struct Crypto1State temp = {t->odd, t->even}; + crypt_word_noret(t, n->uid_xor_nt1, 0); + crypt_word_noret(t, n->nr1_enc, 1); + if(n->ar1_enc == (crypt_word(t) ^ n->p64b)) { + crypto1_get_lfsr(&temp, &(n->key)); + return 1; + } + return 0; + } else if(n->attack == static_nested) { + struct Crypto1State temp = {t->odd, t->even}; + rollback_word_noret(t, n->uid_xor_nt1, 0); + if(n->ks1_1_enc == crypt_word_ret(t, n->uid_xor_nt0, 0)) { + rollback_word_noret(&temp, n->uid_xor_nt1, 0); + crypto1_get_lfsr(&temp, &(n->key)); + return 1; + } + return 0; + } + return 0; +} + +static inline int state_loop( + unsigned int* states_buffer, + int xks, + int m1, + int m2, + unsigned int in, + uint8_t and_val) { + int states_tail = 0; + int round = 0, s = 0, xks_bit = 0, round_in = 0; + + for(round = 1; round <= 12; round++) { + xks_bit = BIT(xks, round); + if(round > 4) { + round_in = ((in >> (2 * (round - 4))) & and_val) << 24; + } + + for(s = 0; s <= states_tail; s++) { + states_buffer[s] <<= 1; + + if((filter(states_buffer[s]) ^ filter(states_buffer[s] | 1)) != 0) { + states_buffer[s] |= filter(states_buffer[s]) ^ xks_bit; + if(round > 4) { + update_contribution(states_buffer, s, m1, m2); + states_buffer[s] ^= round_in; + } + } else if(filter(states_buffer[s]) == xks_bit) { + // TODO: Refactor + if(round > 4) { + states_buffer[++states_tail] = states_buffer[s + 1]; + states_buffer[s + 1] = states_buffer[s] | 1; + update_contribution(states_buffer, s, m1, m2); + states_buffer[s++] ^= round_in; + update_contribution(states_buffer, s, m1, m2); + states_buffer[s] ^= round_in; + } else { + states_buffer[++states_tail] = states_buffer[++s]; + states_buffer[s] = states_buffer[s - 1] | 1; + } + } else { + states_buffer[s--] = states_buffer[states_tail--]; + } + } + } + + return states_tail; +} + +int binsearch(unsigned int data[], int start, int stop) { + int mid, val = data[stop] & 0xff000000; + while(start != stop) { + mid = (stop - start) >> 1; + if((data[start + mid] ^ 0x80000000) > (val ^ 0x80000000)) + stop = start + mid; + else + start += mid + 1; + } + return start; +} +void quicksort(unsigned int array[], int low, int high) { + //if (SIZEOF(array) == 0) + // return; + if(low >= high) return; + int middle = low + (high - low) / 2; + unsigned int pivot = array[middle]; + int i = low, j = high; + while(i <= j) { + while(array[i] < pivot) { + i++; + } + while(array[j] > pivot) { + j--; + } + if(i <= j) { // swap + int temp = array[i]; + array[i] = array[j]; + array[j] = temp; + i++; + j--; + } + } + if(low < j) { + quicksort(array, low, j); + } + if(high > i) { + quicksort(array, i, high); + } +} +int extend_table(unsigned int data[], int tbl, int end, int bit, int m1, int m2, unsigned int in) { + in <<= 24; + for(data[tbl] <<= 1; tbl <= end; data[++tbl] <<= 1) { + if((filter(data[tbl]) ^ filter(data[tbl] | 1)) != 0) { + data[tbl] |= filter(data[tbl]) ^ bit; + update_contribution(data, tbl, m1, m2); + data[tbl] ^= in; + } else if(filter(data[tbl]) == bit) { + data[++end] = data[tbl + 1]; + data[tbl + 1] = data[tbl] | 1; + update_contribution(data, tbl, m1, m2); + data[tbl++] ^= in; + update_contribution(data, tbl, m1, m2); + data[tbl] ^= in; + } else { + data[tbl--] = data[end--]; + } + } + return end; +} + +int old_recover( + unsigned int odd[], + int o_head, + int o_tail, + int oks, + unsigned int even[], + int e_head, + int e_tail, + int eks, + int rem, + int s, + MfClassicNonce* n, + unsigned int in, + int first_run) { + int o, e, i; + if(rem == -1) { + for(e = e_head; e <= e_tail; ++e) { + even[e] = (even[e] << 1) ^ evenparity32(even[e] & LF_POLY_EVEN) ^ (!!(in & 4)); + for(o = o_head; o <= o_tail; ++o, ++s) { + struct Crypto1State temp = {0, 0}; + temp.even = odd[o]; + temp.odd = even[e] ^ evenparity32(odd[o] & LF_POLY_ODD); + if(check_state(&temp, n)) { + return -1; + } + } + } + return s; + } + if(first_run == 0) { + for(i = 0; (i < 4) && (rem-- != 0); i++) { + oks >>= 1; + eks >>= 1; + in >>= 2; + o_tail = extend_table( + odd, o_head, o_tail, oks & 1, LF_POLY_EVEN << 1 | 1, LF_POLY_ODD << 1, 0); + if(o_head > o_tail) return s; + e_tail = extend_table( + even, e_head, e_tail, eks & 1, LF_POLY_ODD, LF_POLY_EVEN << 1 | 1, in & 3); + if(e_head > e_tail) return s; + } + } + first_run = 0; + quicksort(odd, o_head, o_tail); + quicksort(even, e_head, e_tail); + while(o_tail >= o_head && e_tail >= e_head) { + if(((odd[o_tail] ^ even[e_tail]) >> 24) == 0) { + o_tail = binsearch(odd, o_head, o = o_tail); + e_tail = binsearch(even, e_head, e = e_tail); + s = old_recover( + odd, o_tail--, o, oks, even, e_tail--, e, eks, rem, s, n, in, first_run); + if(s == -1) { + break; + } + } else if((odd[o_tail] ^ 0x80000000) > (even[e_tail] ^ 0x80000000)) { + o_tail = binsearch(odd, o_head, o_tail) - 1; + } else { + e_tail = binsearch(even, e_head, e_tail) - 1; + } + } + return s; +} + +static inline int sync_state(ProgramState* program_state) { + int ts = furi_hal_rtc_get_timestamp(); + int elapsed_time = ts - program_state->eta_timestamp; + if(elapsed_time < program_state->eta_round) { + program_state->eta_round -= elapsed_time; + } else { + program_state->eta_round = 0; + } + if(elapsed_time < program_state->eta_total) { + program_state->eta_total -= elapsed_time; + } else { + program_state->eta_total = 0; + } + program_state->eta_timestamp = ts; + if(program_state->close_thread_please) { + return 1; + } + return 0; +} + +int calculate_msb_tables( + int oks, + int eks, + int msb_round, + MfClassicNonce* n, + unsigned int* states_buffer, + struct Msb* odd_msbs, + struct Msb* even_msbs, + unsigned int* temp_states_odd, + unsigned int* temp_states_even, + unsigned int in, + ProgramState* program_state) { + //FURI_LOG_I(TAG, "MSB GO %i", msb_iter); // DEBUG + unsigned int msb_head = (MSB_LIMIT * msb_round); // msb_iter ranges from 0 to (256/MSB_LIMIT)-1 + unsigned int msb_tail = (MSB_LIMIT * (msb_round + 1)); + int states_tail = 0, tail = 0; + int i = 0, j = 0, semi_state = 0, found = 0; + unsigned int msb = 0; + in = ((in >> 16 & 0xff) | (in << 16) | (in & 0xff00)) << 1; + // TODO: Why is this necessary? + memset(odd_msbs, 0, MSB_LIMIT * sizeof(struct Msb)); + memset(even_msbs, 0, MSB_LIMIT * sizeof(struct Msb)); + + for(semi_state = 1 << 20; semi_state >= 0; semi_state--) { + if(semi_state % 32768 == 0) { + if(sync_state(program_state) == 1) { + return 0; + } + } + + if(filter(semi_state) == (oks & 1)) { //-V547 + states_buffer[0] = semi_state; + states_tail = state_loop(states_buffer, oks, CONST_M1_1, CONST_M2_1, 0, 0); + + for(i = states_tail; i >= 0; i--) { + msb = states_buffer[i] >> 24; + if((msb >= msb_head) && (msb < msb_tail)) { + found = 0; + for(j = 0; j < odd_msbs[msb - msb_head].tail - 1; j++) { + if(odd_msbs[msb - msb_head].states[j] == states_buffer[i]) { + found = 1; + break; + } + } + + if(!found) { + tail = odd_msbs[msb - msb_head].tail++; + odd_msbs[msb - msb_head].states[tail] = states_buffer[i]; + } + } + } + } + + if(filter(semi_state) == (eks & 1)) { //-V547 + states_buffer[0] = semi_state; + states_tail = state_loop(states_buffer, eks, CONST_M1_2, CONST_M2_2, in, 3); + + for(i = 0; i <= states_tail; i++) { + msb = states_buffer[i] >> 24; + if((msb >= msb_head) && (msb < msb_tail)) { + found = 0; + + for(j = 0; j < even_msbs[msb - msb_head].tail; j++) { + if(even_msbs[msb - msb_head].states[j] == states_buffer[i]) { + found = 1; + break; + } + } + + if(!found) { + tail = even_msbs[msb - msb_head].tail++; + even_msbs[msb - msb_head].states[tail] = states_buffer[i]; + } + } + } + } + } + + oks >>= 12; + eks >>= 12; + + for(i = 0; i < MSB_LIMIT; i++) { + if(sync_state(program_state) == 1) { + return 0; + } + // TODO: Why is this necessary? + memset(temp_states_even, 0, sizeof(unsigned int) * (1280)); + memset(temp_states_odd, 0, sizeof(unsigned int) * (1280)); + memcpy(temp_states_odd, odd_msbs[i].states, odd_msbs[i].tail * sizeof(unsigned int)); + memcpy(temp_states_even, even_msbs[i].states, even_msbs[i].tail * sizeof(unsigned int)); + int res = old_recover( + temp_states_odd, + 0, + odd_msbs[i].tail, + oks, + temp_states_even, + 0, + even_msbs[i].tail, + eks, + 3, + 0, + n, + in >> 16, + 1); + if(res == -1) { + return 1; + } + //odd_msbs[i].tail = 0; + //even_msbs[i].tail = 0; + } + + return 0; +} + +void** allocate_blocks(const size_t* block_sizes, int num_blocks) { + void** block_pointers = malloc(num_blocks * sizeof(void*)); + if(block_pointers == NULL) { + return NULL; + } + + for(int i = 0; i < num_blocks; i++) { + if(memmgr_heap_get_max_free_block() < block_sizes[i]) { + // Not enough memory, free previously allocated blocks + for(int j = 0; j < i; j++) { + free(block_pointers[j]); + } + free(block_pointers); + return NULL; + } + + block_pointers[i] = malloc(block_sizes[i]); + if(block_pointers[i] == NULL) { + // Allocation failed, free previously allocated blocks + for(int j = 0; j < i; j++) { + free(block_pointers[j]); + } + free(block_pointers); + return NULL; + } + } + + return block_pointers; +} + +bool recover(MfClassicNonce* n, int ks2, unsigned int in, ProgramState* program_state) { + bool found = false; + const size_t block_sizes[] = {49216, 49216, 5120, 5120, 4096}; + const size_t reduced_block_sizes[] = {24608, 24608, 5120, 5120, 4096}; + const int num_blocks = sizeof(block_sizes) / sizeof(block_sizes[0]); + void** block_pointers = allocate_blocks(block_sizes, num_blocks); + if(block_pointers == NULL) { + // System has less than the guaranteed amount of RAM (140 KB) - adjust some parameters to run anyway at half speed + eta_round_time *= 2; + eta_total_time *= 2; + MSB_LIMIT /= 2; + block_pointers = allocate_blocks(reduced_block_sizes, num_blocks); + if(block_pointers == NULL) { + // System has less than 70 KB of RAM - should never happen so we don't reduce speed further + program_state->err = InsufficientRAM; + program_state->mfkey_state = Error; + return false; + } + } + struct Msb* odd_msbs = block_pointers[0]; + struct Msb* even_msbs = block_pointers[1]; + unsigned int* temp_states_odd = block_pointers[2]; + unsigned int* temp_states_even = block_pointers[3]; + unsigned int* states_buffer = block_pointers[4]; + int oks = 0, eks = 0; + int i = 0, msb = 0; + for(i = 31; i >= 0; i -= 2) { + oks = oks << 1 | BEBIT(ks2, i); + } + for(i = 30; i >= 0; i -= 2) { + eks = eks << 1 | BEBIT(ks2, i); + } + int bench_start = furi_hal_rtc_get_timestamp(); + program_state->eta_total = eta_total_time; + program_state->eta_timestamp = bench_start; + for(msb = 0; msb <= ((256 / MSB_LIMIT) - 1); msb++) { + program_state->search = msb; + program_state->eta_round = eta_round_time; + program_state->eta_total = eta_total_time - (eta_round_time * msb); + if(calculate_msb_tables( + oks, + eks, + msb, + n, + states_buffer, + odd_msbs, + even_msbs, + temp_states_odd, + temp_states_even, + in, + program_state)) { + //int bench_stop = furi_hal_rtc_get_timestamp(); + //FURI_LOG_I(TAG, "Cracked in %i seconds", bench_stop - bench_start); + found = true; + break; + } + if(program_state->close_thread_please) { + break; + } + } + // Free the allocated blocks + for(int i = 0; i < num_blocks; i++) { + free(block_pointers[i]); + } + free(block_pointers); + return found; +} + +bool key_already_found_for_nonce_in_solved( + MfClassicKey* keyarray, + int keyarray_size, + MfClassicNonce* nonce) { + for(int k = 0; k < keyarray_size; k++) { + uint64_t key_as_int = bit_lib_bytes_to_num_be(keyarray[k].data, sizeof(MfClassicKey)); + struct Crypto1State temp = {0, 0}; + for(int i = 0; i < 24; i++) { + (&temp)->odd |= (BIT(key_as_int, 2 * i + 1) << (i ^ 3)); + (&temp)->even |= (BIT(key_as_int, 2 * i) << (i ^ 3)); + } + if(nonce->attack == mfkey32) { + crypt_word_noret(&temp, nonce->uid_xor_nt1, 0); + crypt_word_noret(&temp, nonce->nr1_enc, 1); + if(nonce->ar1_enc == (crypt_word(&temp) ^ nonce->p64b)) { + return true; + } + } else if(nonce->attack == static_nested) { + uint32_t expected_ks1 = crypt_word_ret(&temp, nonce->uid_xor_nt0, 0); + if(nonce->ks1_1_enc == expected_ks1) { + return true; + } + } + } + return false; +} + +#pragma GCC push_options +#pragma GCC optimize("Os") +static void finished_beep() { + // Beep to indicate completion + NotificationApp* notification = furi_record_open("notification"); + notification_message(notification, &sequence_audiovisual_alert); + notification_message(notification, &sequence_display_backlight_on); + furi_record_close("notification"); +} + +void mfkey(ProgramState* program_state) { + MfClassicKey found_key; // recovered key + size_t keyarray_size = 0; + MfClassicKey* keyarray = malloc(sizeof(MfClassicKey) * 1); + uint32_t i = 0, j = 0; + //FURI_LOG_I(TAG, "Free heap before alloc(): %zub", memmgr_get_free_heap()); + Storage* storage = furi_record_open(RECORD_STORAGE); + FlipperApplication* app = flipper_application_alloc(storage, firmware_api_interface); + flipper_application_preload(app, APP_DATA_PATH("plugins/mfkey_init_plugin.fal")); + flipper_application_map_to_memory(app); + const FlipperAppPluginDescriptor* app_descriptor = + flipper_application_plugin_get_descriptor(app); + const MfkeyPlugin* init_plugin = app_descriptor->entry_point; + // Check for nonces + program_state->mfkey32_present = init_plugin->napi_mf_classic_mfkey32_nonces_check_presence(); + program_state->nested_present = init_plugin->napi_mf_classic_nested_nonces_check_presence(); + if(!(program_state->mfkey32_present) && !(program_state->nested_present)) { + program_state->err = MissingNonces; + program_state->mfkey_state = Error; + flipper_application_free(app); + furi_record_close(RECORD_STORAGE); + free(keyarray); + return; + } + // Read dictionaries (optional) + KeysDict* system_dict = {0}; + bool system_dict_exists = keys_dict_check_presence(KEYS_DICT_SYSTEM_PATH); + KeysDict* user_dict = {0}; + bool user_dict_exists = keys_dict_check_presence(KEYS_DICT_USER_PATH); + uint32_t total_dict_keys = 0; + if(system_dict_exists) { + system_dict = + keys_dict_alloc(KEYS_DICT_SYSTEM_PATH, KeysDictModeOpenExisting, sizeof(MfClassicKey)); + total_dict_keys += keys_dict_get_total_keys(system_dict); + } + user_dict = keys_dict_alloc(KEYS_DICT_USER_PATH, KeysDictModeOpenAlways, sizeof(MfClassicKey)); + if(user_dict_exists) { + total_dict_keys += keys_dict_get_total_keys(user_dict); + } + user_dict_exists = true; + program_state->dict_count = total_dict_keys; + program_state->mfkey_state = DictionaryAttack; + // Read nonces + MfClassicNonceArray* nonce_arr; + nonce_arr = init_plugin->napi_mf_classic_nonce_array_alloc( + system_dict, system_dict_exists, user_dict, program_state); + if(system_dict_exists) { + keys_dict_free(system_dict); + } + if(nonce_arr->total_nonces == 0) { + // Nothing to crack + program_state->err = ZeroNonces; + program_state->mfkey_state = Error; + init_plugin->napi_mf_classic_nonce_array_free(nonce_arr); + flipper_application_free(app); + furi_record_close(RECORD_STORAGE); + keys_dict_free(user_dict); + free(keyarray); + return; + } + flipper_application_free(app); + furi_record_close(RECORD_STORAGE); + // TODO: Track free state at the time this is called to ensure double free does not happen + furi_assert(nonce_arr); + furi_assert(nonce_arr->stream); + buffered_file_stream_close(nonce_arr->stream); + stream_free(nonce_arr->stream); + //FURI_LOG_I(TAG, "Free heap after free(): %zub", memmgr_get_free_heap()); + program_state->mfkey_state = MFKeyAttack; + // TODO: Work backwards on this array and free memory + for(i = 0; i < nonce_arr->total_nonces; i++) { + MfClassicNonce next_nonce = nonce_arr->remaining_nonce_array[i]; + if(key_already_found_for_nonce_in_solved(keyarray, keyarray_size, &next_nonce)) { + nonce_arr->remaining_nonces--; + (program_state->cracked)++; + (program_state->num_completed)++; + continue; + } + //FURI_LOG_I(TAG, "Beginning recovery for %8lx", next_nonce.uid); + if(next_nonce.attack == mfkey32) { + if(!recover(&next_nonce, next_nonce.ar0_enc ^ next_nonce.p64, 0, program_state)) { + if(program_state->close_thread_please) { + break; + } + // No key found in recover() + (program_state->num_completed)++; + continue; + } + } else if(next_nonce.attack == static_nested) { + if(!recover( + &next_nonce, + next_nonce.ks1_2_enc, + next_nonce.nt1 ^ next_nonce.uid, + program_state)) { + if(program_state->close_thread_please) { + break; + } + // No key found in recover() + (program_state->num_completed)++; + continue; + } + } + (program_state->cracked)++; + (program_state->num_completed)++; + found_key = next_nonce.key; + bool already_found = false; + for(j = 0; j < keyarray_size; j++) { + if(memcmp(keyarray[j].data, found_key.data, MF_CLASSIC_KEY_SIZE) == 0) { + already_found = true; + break; + } + } + if(already_found == false) { + // New key + keyarray = realloc(keyarray, sizeof(MfClassicKey) * (keyarray_size + 1)); //-V701 + keyarray_size += 1; + keyarray[keyarray_size - 1] = found_key; + (program_state->unique_cracked)++; + } + } + // TODO: Update display to show all keys were found + // TODO: Prepend found key(s) to user dictionary file + //FURI_LOG_I(TAG, "Unique keys found:"); + for(i = 0; i < keyarray_size; i++) { + //FURI_LOG_I(TAG, "%012" PRIx64, keyarray[i]); + keys_dict_add_key(user_dict, keyarray[i].data, sizeof(MfClassicKey)); + } + if(keyarray_size > 0) { + dolphin_deed(DolphinDeedNfcMfcAdd); + } + free(nonce_arr); + keys_dict_free(user_dict); + free(keyarray); + if(program_state->mfkey_state == Error) { + return; + } + //FURI_LOG_I(TAG, "mfkey function completed normally"); // DEBUG + program_state->mfkey_state = Complete; + // No need to alert the user if they asked it to stop + if(!(program_state->close_thread_please)) { + finished_beep(); + } + return; +} + +// Screen is 128x64 px +static void render_callback(Canvas* const canvas, void* ctx) { + furi_assert(ctx); + ProgramState* program_state = ctx; + furi_mutex_acquire(program_state->mutex, FuriWaitForever); + char draw_str[44] = {}; + canvas_clear(canvas); + canvas_draw_frame(canvas, 0, 0, 128, 64); + canvas_draw_frame(canvas, 0, 15, 128, 64); + canvas_set_font(canvas, FontPrimary); + canvas_draw_str_aligned(canvas, 5, 4, AlignLeft, AlignTop, "MFKey"); + snprintf(draw_str, sizeof(draw_str), "RAM: %zub", memmgr_get_free_heap()); + canvas_set_font(canvas, FontSecondary); + canvas_draw_str_aligned(canvas, 48, 5, AlignLeft, AlignTop, draw_str); + canvas_draw_icon(canvas, 114, 4, &I_mfkey); + if(program_state->is_thread_running && program_state->mfkey_state == MFKeyAttack) { + float eta_round = (float)1 - ((float)program_state->eta_round / (float)eta_round_time); + float eta_total = (float)1 - ((float)program_state->eta_total / (float)eta_total_time); + float progress = (float)program_state->num_completed / (float)program_state->total; + if(eta_round < 0) { + // Round ETA miscalculated + eta_round = 1; + program_state->eta_round = 0; + } + if(eta_total < 0) { + // Total ETA miscalculated + eta_total = 1; + program_state->eta_total = 0; + } + canvas_set_font(canvas, FontSecondary); + snprintf( + draw_str, + sizeof(draw_str), + "Cracking: %d/%d - in prog.", + program_state->num_completed, + program_state->total); + elements_progress_bar_with_text(canvas, 5, 18, 118, progress, draw_str); + snprintf( + draw_str, + sizeof(draw_str), + "Round: %d/%d - ETA %02d Sec", + (program_state->search) + 1, // Zero indexed + 256 / MSB_LIMIT, + program_state->eta_round); + elements_progress_bar_with_text(canvas, 5, 31, 118, eta_round, draw_str); + snprintf(draw_str, sizeof(draw_str), "Total ETA %03d Sec", program_state->eta_total); + elements_progress_bar_with_text(canvas, 5, 44, 118, eta_total, draw_str); + } else if(program_state->is_thread_running && program_state->mfkey_state == DictionaryAttack) { + canvas_set_font(canvas, FontSecondary); + snprintf( + draw_str, sizeof(draw_str), "Dict solves: %d (in progress)", program_state->cracked); + canvas_draw_str_aligned(canvas, 10, 18, AlignLeft, AlignTop, draw_str); + snprintf(draw_str, sizeof(draw_str), "Keys in dict: %d", program_state->dict_count); + canvas_draw_str_aligned(canvas, 26, 28, AlignLeft, AlignTop, draw_str); + } else if(program_state->mfkey_state == Complete) { + // TODO: Scrollable list view to see cracked keys if user presses down + elements_progress_bar_with_text(canvas, 5, 18, 118, 1, draw_str); + canvas_set_font(canvas, FontSecondary); + snprintf(draw_str, sizeof(draw_str), "Complete"); + canvas_draw_str_aligned(canvas, 40, 31, AlignLeft, AlignTop, draw_str); + snprintf( + draw_str, + sizeof(draw_str), + "Keys added to user dict: %d", + program_state->unique_cracked); + canvas_draw_str_aligned(canvas, 10, 41, AlignLeft, AlignTop, draw_str); + } else if(program_state->mfkey_state == Ready) { + canvas_set_font(canvas, FontSecondary); + canvas_draw_str_aligned(canvas, 50, 30, AlignLeft, AlignTop, "Ready"); + elements_button_center(canvas, "Start"); + elements_button_right(canvas, "Help"); + } else if(program_state->mfkey_state == Help) { + canvas_set_font(canvas, FontSecondary); + canvas_draw_str_aligned(canvas, 7, 20, AlignLeft, AlignTop, "Collect nonces using Detect"); + canvas_draw_str_aligned(canvas, 7, 30, AlignLeft, AlignTop, "Reader or FlipperNested."); + canvas_draw_str_aligned(canvas, 7, 40, AlignLeft, AlignTop, "Devs: noproto, AG, ALiberty"); + canvas_draw_str_aligned(canvas, 7, 50, AlignLeft, AlignTop, "Thanks: bettse, Foxushka"); + } else if(program_state->mfkey_state == Error) { + canvas_draw_str_aligned(canvas, 50, 25, AlignLeft, AlignTop, "Error"); + canvas_set_font(canvas, FontSecondary); + if(program_state->err == MissingNonces) { + canvas_draw_str_aligned(canvas, 25, 36, AlignLeft, AlignTop, "No nonces found"); + } else if(program_state->err == ZeroNonces) { + canvas_draw_str_aligned(canvas, 15, 36, AlignLeft, AlignTop, "Nonces already cracked"); + } else if(program_state->err == InsufficientRAM) { + canvas_draw_str_aligned(canvas, 35, 36, AlignLeft, AlignTop, "No free RAM"); + } else { + // Unhandled error + } + } else { + // Unhandled program state + } + furi_mutex_release(program_state->mutex); +} + +static void input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) { + furi_assert(event_queue); + + PluginEvent event = {.type = EventTypeKey, .input = *input_event}; + furi_message_queue_put(event_queue, &event, FuriWaitForever); +} + +static void mfkey_state_init(ProgramState* program_state) { + program_state->is_thread_running = false; + program_state->mfkey_state = Ready; + program_state->cracked = 0; + program_state->unique_cracked = 0; + program_state->num_completed = 0; + program_state->total = 0; + program_state->dict_count = 0; +} + +// Entrypoint for worker thread +static int32_t mfkey_worker_thread(void* ctx) { + ProgramState* program_state = ctx; + program_state->is_thread_running = true; + program_state->mfkey_state = Initializing; + //FURI_LOG_I(TAG, "Hello from the mfkey worker thread"); // DEBUG + mfkey(program_state); + program_state->is_thread_running = false; + return 0; +} + +void start_mfkey_thread(ProgramState* program_state) { + if(!program_state->is_thread_running) { + furi_thread_start(program_state->mfkeythread); + } +} + +int32_t mfkey_main() { + FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(PluginEvent)); + + ProgramState* program_state = malloc(sizeof(ProgramState)); + + mfkey_state_init(program_state); + + program_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal); + if(!program_state->mutex) { + //FURI_LOG_E(TAG, "cannot create mutex\r\n"); + free(program_state); + return 255; + } + + // Set system callbacks + ViewPort* view_port = view_port_alloc(); + view_port_draw_callback_set(view_port, render_callback, program_state); + view_port_input_callback_set(view_port, input_callback, event_queue); + + // Open GUI and register view_port + Gui* gui = furi_record_open(RECORD_GUI); + gui_add_view_port(gui, view_port, GuiLayerFullscreen); + + program_state->mfkeythread = furi_thread_alloc(); + furi_thread_set_name(program_state->mfkeythread, "MFKey Worker"); + furi_thread_set_stack_size(program_state->mfkeythread, 2048); + furi_thread_set_context(program_state->mfkeythread, program_state); + furi_thread_set_callback(program_state->mfkeythread, mfkey_worker_thread); + + PluginEvent event; + for(bool main_loop = true; main_loop;) { + FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100); + + furi_mutex_acquire(program_state->mutex, FuriWaitForever); + + if(event_status == FuriStatusOk) { + // press events + if(event.type == EventTypeKey) { + if(event.input.type == InputTypePress) { + switch(event.input.key) { + case InputKeyUp: + break; + case InputKeyDown: + break; + case InputKeyRight: + if(!program_state->is_thread_running && + program_state->mfkey_state == Ready) { + program_state->mfkey_state = Help; + view_port_update(view_port); + } + break; + case InputKeyLeft: + break; + case InputKeyOk: + if(!program_state->is_thread_running && + program_state->mfkey_state == Ready) { + start_mfkey_thread(program_state); + view_port_update(view_port); + } + break; + case InputKeyBack: + if(!program_state->is_thread_running && + program_state->mfkey_state == Help) { + program_state->mfkey_state = Ready; + view_port_update(view_port); + } else { + program_state->close_thread_please = true; + if(program_state->is_thread_running && program_state->mfkeythread) { + // Wait until thread is finished + furi_thread_join(program_state->mfkeythread); + } + program_state->close_thread_please = false; + main_loop = false; + } + break; + default: + break; + } + } + } + } + + view_port_update(view_port); + furi_mutex_release(program_state->mutex); + } + + furi_thread_free(program_state->mfkeythread); + view_port_enabled_set(view_port, false); + gui_remove_view_port(gui, view_port); + furi_record_close("gui"); + view_port_free(view_port); + furi_message_queue_free(event_queue); + furi_mutex_free(program_state->mutex); + free(program_state); + + return 0; +} +#pragma GCC pop_options \ No newline at end of file diff --git a/mfkey/mfkey.h b/mfkey/mfkey.h new file mode 100644 index 00000000..a086ca67 --- /dev/null +++ b/mfkey/mfkey.h @@ -0,0 +1,107 @@ +#ifndef MFKEY_H +#define MFKEY_H + +#include +#include +#include +#include +#include +#include +#include + +struct Crypto1State { + uint32_t odd, even; +}; +struct Msb { + int tail; + uint32_t states[768]; +}; + +typedef enum { + EventTypeTick, + EventTypeKey, +} EventType; + +typedef struct { + EventType type; + InputEvent input; +} PluginEvent; + +typedef enum { + MissingNonces, + ZeroNonces, + InsufficientRAM, +} MFKeyError; + +typedef enum { + Ready, + Initializing, + DictionaryAttack, + MFKeyAttack, + Complete, + Error, + Help, +} MFKeyState; + +// TODO: Can we eliminate any of the members of this struct? +typedef struct { + FuriMutex* mutex; + MFKeyError err; + MFKeyState mfkey_state; + int cracked; + int unique_cracked; + int num_completed; + int total; + int dict_count; + int search; + int eta_timestamp; + int eta_total; + int eta_round; + bool mfkey32_present; + bool nested_present; + bool is_thread_running; + bool close_thread_please; + FuriThread* mfkeythread; +} ProgramState; + +typedef enum { mfkey32, static_nested } AttackType; + +typedef struct { + AttackType attack; + MfClassicKey key; // key + uint32_t uid; // serial number + uint32_t nt0; // tag challenge first + uint32_t nt1; // tag challenge second + uint32_t uid_xor_nt0; // uid ^ nt0 + uint32_t uid_xor_nt1; // uid ^ nt1 + // Mfkey32 + uint32_t p64; // 64th successor of nt0 + uint32_t p64b; // 64th successor of nt1 + uint32_t nr0_enc; // first encrypted reader challenge + uint32_t ar0_enc; // first encrypted reader response + uint32_t nr1_enc; // second encrypted reader challenge + uint32_t ar1_enc; // second encrypted reader response + // Nested + uint32_t ks1_1_enc; // first encrypted keystream + uint32_t ks1_2_enc; // second encrypted keystream + char par_1_str[5]; // first parity bits (string representation) + char par_2_str[5]; // second parity bits (string representation) + uint8_t par_1; // first parity bits + uint8_t par_2; // second parity bits +} MfClassicNonce; + +typedef struct { + Stream* stream; + uint32_t total_nonces; + MfClassicNonce* remaining_nonce_array; + size_t remaining_nonces; +} MfClassicNonceArray; + +struct KeysDict { + Stream* stream; + size_t key_size; + size_t key_size_symbols; + size_t total_keys; +}; + +#endif // MFKEY_H \ No newline at end of file diff --git a/mfkey/mfkey.png b/mfkey/mfkey.png new file mode 100644 index 0000000000000000000000000000000000000000..f1694bb335a8619e53cd3b98651ba995cc7a1caf GIT binary patch literal 107 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2xGmzZ=C-xtZVhivIasB`QKad%E=yDy9Qt)(f z4B@z*{KD=)L3iUrapuFX*xK~i1d{R-5*QT~m>F7Tu$Q|1zuOE{%i!ti=d#Wzp$P!I C{~rec literal 0 HcmV?d00001 diff --git a/mfkey/plugin_interface.h b/mfkey/plugin_interface.h new file mode 100644 index 00000000..2bccba10 --- /dev/null +++ b/mfkey/plugin_interface.h @@ -0,0 +1,13 @@ +#pragma once + +#define PLUGIN_APP_ID "mfkey" +#define PLUGIN_API_VERSION 1 + +typedef struct { + const char* name; + bool (*napi_mf_classic_mfkey32_nonces_check_presence)(); + bool (*napi_mf_classic_nested_nonces_check_presence)(); + MfClassicNonceArray* ( + *napi_mf_classic_nonce_array_alloc)(KeysDict*, bool, KeysDict*, ProgramState*); + void (*napi_mf_classic_nonce_array_free)(MfClassicNonceArray*); +} MfkeyPlugin; \ No newline at end of file diff --git a/mfkey32/.catalog/README.md b/mfkey32/.catalog/README.md deleted file mode 100644 index 81bbcd60..00000000 --- a/mfkey32/.catalog/README.md +++ /dev/null @@ -1,12 +0,0 @@ -# Flipper Zero MFKey32 - -This application allows you to calculate the keys to Mifare Classic cards from the nonces using the MFkey32 algorithm directly on your Flipper Zero. After collecting the nonces using the Detect Reader feature of the NFC app, they can be used to calculate the keys to the card in the MFKey32 app. - -## Usage - -After collecting nonces using the Detect Reader option, press the Start button in the MFKey32 app and wait for it to finish. The calculation can take more than 10 minutes, so you'll have to be patient. After the calculation is complete, the keys will be saved to the user key dictionary. - -## Credits - -Developers: noproto, AG -Thanks: bettse diff --git a/mfkey32/.catalog/changelog.md b/mfkey32/.catalog/changelog.md deleted file mode 100644 index 4eec4817..00000000 --- a/mfkey32/.catalog/changelog.md +++ /dev/null @@ -1,4 +0,0 @@ -## 1.1 - - Rework application with new NFC API -## 1.0 - - Initial release diff --git a/mfkey32/.catalog/screenshots/1.png b/mfkey32/.catalog/screenshots/1.png deleted file mode 100644 index 7c4839045672d8fd8cd7bad4dbd7caeca828e5fb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2077 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 diff --git a/mfkey32/.catalog/screenshots/3.png b/mfkey32/.catalog/screenshots/3.png deleted file mode 100644 index c4dffb8a65c94d8c0d4672d88354a6b307fcf03c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2162 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 T2_+RDfDS|^SsNGcaF6&O#taN} diff --git a/mfkey32/mfkey.png b/mfkey32/mfkey.png deleted file mode 100644 index 52ab29efb92bf5aa7c790b8043d2d07a7e74704d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9060 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 T2_+RDfDS|^SsNGcaF6&O#taN} diff --git a/mfkey32/mfkey32.c b/mfkey32/mfkey32.c deleted file mode 100644 index df92b1e4..00000000 --- a/mfkey32/mfkey32.c +++ /dev/null @@ -1,1354 +0,0 @@ -#pragma GCC optimize("O3") -#pragma GCC optimize("-funroll-all-loops") - -// TODO: Add keys to top of the user dictionary, not the bottom -// TODO: More efficient dictionary bruteforce by scanning through hardcoded very common keys and previously found dictionary keys first? -// (a cache for napi_key_already_found_for_nonce) - -#include -#include -#include "time.h" -#include -#include -#include -#include -#include "mfkey32_icons.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define MF_CLASSIC_DICT_FLIPPER_PATH EXT_PATH("nfc/assets/mf_classic_dict.nfc") -#define MF_CLASSIC_DICT_USER_PATH EXT_PATH("nfc/assets/mf_classic_dict_user.nfc") -#define MF_CLASSIC_NONCE_PATH EXT_PATH("nfc/.mfkey32.log") -#define TAG "Mfkey32" -#define NFC_MF_CLASSIC_KEY_LEN (13) - -#define MIN_RAM 115632 -#define LF_POLY_ODD (0x29CE5C) -#define LF_POLY_EVEN (0x870804) -#define CONST_M1_1 (LF_POLY_EVEN << 1 | 1) -#define CONST_M2_1 (LF_POLY_ODD << 1) -#define CONST_M1_2 (LF_POLY_ODD) -#define CONST_M2_2 (LF_POLY_EVEN << 1 | 1) -#define BIT(x, n) ((x) >> (n)&1) -#define BEBIT(x, n) BIT(x, (n) ^ 24) -#define SWAPENDIAN(x) \ - ((x) = ((x) >> 8 & 0xff00ff) | ((x)&0xff00ff) << 8, (x) = (x) >> 16 | (x) << 16) -//#define SIZEOF(arr) sizeof(arr) / sizeof(*arr) - -static int eta_round_time = 56; -static int eta_total_time = 900; -// MSB_LIMIT: Chunk size (out of 256) -static int MSB_LIMIT = 16; - -struct Crypto1State { - uint32_t odd, even; -}; -struct Crypto1Params { - uint64_t key; - uint32_t nr0_enc, uid_xor_nt0, uid_xor_nt1, nr1_enc, p64b, ar1_enc; -}; -struct Msb { - int tail; - uint32_t states[768]; -}; - -typedef enum { - EventTypeTick, - EventTypeKey, -} EventType; - -typedef struct { - EventType type; - InputEvent input; -} PluginEvent; - -typedef enum { - MissingNonces, - ZeroNonces, -} MfkeyError; - -typedef enum { - Ready, - Initializing, - DictionaryAttack, - MfkeyAttack, - Complete, - Error, - Help, -} MfkeyState; - -// TODO: Can we eliminate any of the members of this struct? -typedef struct { - FuriMutex* mutex; - MfkeyError err; - MfkeyState mfkey_state; - int cracked; - int unique_cracked; - int num_completed; - int total; - int dict_count; - int search; - int eta_timestamp; - int eta_total; - int eta_round; - bool is_thread_running; - bool close_thread_please; - FuriThread* mfkeythread; -} ProgramState; - -// TODO: Merge this with Crypto1Params? -typedef struct { - uint32_t uid; // serial number - uint32_t nt0; // tag challenge first - uint32_t nt1; // tag challenge second - uint32_t nr0_enc; // first encrypted reader challenge - uint32_t ar0_enc; // first encrypted reader response - uint32_t nr1_enc; // second encrypted reader challenge - uint32_t ar1_enc; // second encrypted reader response -} MfClassicNonce; - -typedef struct { - Stream* stream; - uint32_t total_nonces; - MfClassicNonce* remaining_nonce_array; - size_t remaining_nonces; -} MfClassicNonceArray; - -typedef enum { - MfClassicDictTypeSystem, - MfClassicDictTypeUser, -} MfClassicDictType; - -typedef struct { - Stream* stream; - uint32_t total_keys; -} MfClassicDict; - -static const uint8_t table[256] = { - 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, - 4, 4, 5, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, - 4, 5, 4, 5, 5, 6, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, 3, 3, 4, 3, 4, 4, - 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 3, 4, 4, 5, - 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 2, - 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, - 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, - 5, 4, 5, 5, 6, 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 3, 4, 4, 5, 4, 5, 5, 6, - 4, 5, 5, 6, 5, 6, 6, 7, 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8}; -static const uint8_t lookup1[256] = { - 0, 0, 16, 16, 0, 16, 0, 0, 0, 16, 0, 0, 16, 16, 16, 16, 0, 0, 16, 16, 0, 16, 0, 0, - 0, 16, 0, 0, 16, 16, 16, 16, 0, 0, 16, 16, 0, 16, 0, 0, 0, 16, 0, 0, 16, 16, 16, 16, - 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, 8, 8, 24, 24, 8, 24, 8, 8, - 8, 24, 8, 8, 24, 24, 24, 24, 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, - 0, 0, 16, 16, 0, 16, 0, 0, 0, 16, 0, 0, 16, 16, 16, 16, 0, 0, 16, 16, 0, 16, 0, 0, - 0, 16, 0, 0, 16, 16, 16, 16, 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, - 0, 0, 16, 16, 0, 16, 0, 0, 0, 16, 0, 0, 16, 16, 16, 16, 0, 0, 16, 16, 0, 16, 0, 0, - 0, 16, 0, 0, 16, 16, 16, 16, 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, - 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, 0, 0, 16, 16, 0, 16, 0, 0, - 0, 16, 0, 0, 16, 16, 16, 16, 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24, - 8, 8, 24, 24, 8, 24, 8, 8, 8, 24, 8, 8, 24, 24, 24, 24}; -static const uint8_t lookup2[256] = { - 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, - 4, 4, 4, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, - 2, 2, 6, 6, 6, 6, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 2, 2, 6, 6, 2, 6, 2, - 2, 2, 6, 2, 2, 6, 6, 6, 6, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 0, 0, 4, 4, - 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 2, - 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, - 4, 4, 0, 0, 4, 4, 0, 4, 0, 0, 0, 4, 0, 0, 4, 4, 4, 4, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, - 2, 6, 6, 6, 6, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6, 2, 2, 6, 6, 2, 6, 2, 2, - 2, 6, 2, 2, 6, 6, 6, 6, 2, 2, 6, 6, 2, 6, 2, 2, 2, 6, 2, 2, 6, 6, 6, 6}; - -uint32_t prng_successor(uint32_t x, uint32_t n) { - SWAPENDIAN(x); - while(n--) x = x >> 1 | (x >> 16 ^ x >> 18 ^ x >> 19 ^ x >> 21) << 31; - return SWAPENDIAN(x); -} - -static inline int filter(uint32_t const x) { - uint32_t f; - f = lookup1[x & 0xff] | lookup2[(x >> 8) & 0xff]; - f |= 0x0d938 >> (x >> 16 & 0xf) & 1; - return BIT(0xEC57E80A, f); -} - -static inline uint8_t evenparity32(uint32_t x) { - if((table[x & 0xff] + table[(x >> 8) & 0xff] + table[(x >> 16) & 0xff] + table[x >> 24]) % 2 == - 0) { - return 0; - } else { - return 1; - } - //return ((table[x & 0xff] + table[(x >> 8) & 0xff] + table[(x >> 16) & 0xff] + table[x >> 24]) % 2) & 0xFF; -} - -static inline void update_contribution(unsigned int data[], int item, int mask1, int mask2) { - int p = data[item] >> 25; - p = p << 1 | evenparity32(data[item] & mask1); - p = p << 1 | evenparity32(data[item] & mask2); - data[item] = p << 24 | (data[item] & 0xffffff); -} - -void crypto1_get_lfsr(struct Crypto1State* state, uint64_t* lfsr) { - int i; - for(*lfsr = 0, i = 23; i >= 0; --i) { - *lfsr = *lfsr << 1 | BIT(state->odd, i ^ 3); - *lfsr = *lfsr << 1 | BIT(state->even, i ^ 3); - } -} - -static inline uint32_t crypt_word(struct Crypto1State* s) { - // "in" and "x" are always 0 (last iteration) - uint32_t res_ret = 0; - uint32_t feedin, t; - for(int i = 0; i <= 31; i++) { - res_ret |= (filter(s->odd) << (24 ^ i)); //-V629 - feedin = LF_POLY_EVEN & s->even; - feedin ^= LF_POLY_ODD & s->odd; - s->even = s->even << 1 | (evenparity32(feedin)); - t = s->odd, s->odd = s->even, s->even = t; - } - return res_ret; -} - -static inline void crypt_word_noret(struct Crypto1State* s, uint32_t in, int x) { - uint8_t ret; - uint32_t feedin, t, next_in; - for(int i = 0; i <= 31; i++) { - next_in = BEBIT(in, i); - ret = filter(s->odd); - feedin = ret & (!!x); - feedin ^= LF_POLY_EVEN & s->even; - feedin ^= LF_POLY_ODD & s->odd; - feedin ^= !!next_in; - s->even = s->even << 1 | (evenparity32(feedin)); - t = s->odd, s->odd = s->even, s->even = t; - } - return; -} - -static inline void rollback_word_noret(struct Crypto1State* s, uint32_t in, int x) { - uint8_t ret; - uint32_t feedin, t, next_in; - for(int i = 31; i >= 0; i--) { - next_in = BEBIT(in, i); - s->odd &= 0xffffff; - t = s->odd, s->odd = s->even, s->even = t; - ret = filter(s->odd); - feedin = ret & (!!x); - feedin ^= s->even & 1; - feedin ^= LF_POLY_EVEN & (s->even >>= 1); - feedin ^= LF_POLY_ODD & s->odd; - feedin ^= !!next_in; - s->even |= (evenparity32(feedin)) << 23; - } - return; -} - -int key_already_found_for_nonce( - uint64_t* keyarray, - int keyarray_size, - uint32_t uid_xor_nt1, - uint32_t nr1_enc, - uint32_t p64b, - uint32_t ar1_enc) { - for(int k = 0; k < keyarray_size; k++) { - struct Crypto1State temp = {0, 0}; - - for(int i = 0; i < 24; i++) { - (&temp)->odd |= (BIT(keyarray[k], 2 * i + 1) << (i ^ 3)); - (&temp)->even |= (BIT(keyarray[k], 2 * i) << (i ^ 3)); - } - - crypt_word_noret(&temp, uid_xor_nt1, 0); - crypt_word_noret(&temp, nr1_enc, 1); - - if(ar1_enc == (crypt_word(&temp) ^ p64b)) { - return 1; - } - } - return 0; -} - -int check_state(struct Crypto1State* t, struct Crypto1Params* p) { - if(!(t->odd | t->even)) return 0; - rollback_word_noret(t, 0, 0); - rollback_word_noret(t, p->nr0_enc, 1); - rollback_word_noret(t, p->uid_xor_nt0, 0); - struct Crypto1State temp = {t->odd, t->even}; - crypt_word_noret(t, p->uid_xor_nt1, 0); - crypt_word_noret(t, p->nr1_enc, 1); - if(p->ar1_enc == (crypt_word(t) ^ p->p64b)) { - crypto1_get_lfsr(&temp, &(p->key)); - return 1; - } - return 0; -} - -static inline int state_loop(unsigned int* states_buffer, int xks, int m1, int m2) { - int states_tail = 0; - int round = 0, s = 0, xks_bit = 0; - - for(round = 1; round <= 12; round++) { - xks_bit = BIT(xks, round); - - for(s = 0; s <= states_tail; s++) { - states_buffer[s] <<= 1; - - if((filter(states_buffer[s]) ^ filter(states_buffer[s] | 1)) != 0) { - states_buffer[s] |= filter(states_buffer[s]) ^ xks_bit; - if(round > 4) { - update_contribution(states_buffer, s, m1, m2); - } - } else if(filter(states_buffer[s]) == xks_bit) { - // TODO: Refactor - if(round > 4) { - states_buffer[++states_tail] = states_buffer[s + 1]; - states_buffer[s + 1] = states_buffer[s] | 1; - update_contribution(states_buffer, s, m1, m2); - s++; - update_contribution(states_buffer, s, m1, m2); - } else { - states_buffer[++states_tail] = states_buffer[++s]; - states_buffer[s] = states_buffer[s - 1] | 1; - } - } else { - states_buffer[s--] = states_buffer[states_tail--]; - } - } - } - - return states_tail; -} - -int binsearch(unsigned int data[], int start, int stop) { - int mid, val = data[stop] & 0xff000000; - while(start != stop) { - mid = (stop - start) >> 1; - if((data[start + mid] ^ 0x80000000) > (val ^ 0x80000000)) - stop = start + mid; - else - start += mid + 1; - } - return start; -} -void quicksort(unsigned int array[], int low, int high) { - //if (SIZEOF(array) == 0) - // return; - if(low >= high) return; - int middle = low + (high - low) / 2; - unsigned int pivot = array[middle]; - int i = low, j = high; - while(i <= j) { - while(array[i] < pivot) { - i++; - } - while(array[j] > pivot) { - j--; - } - if(i <= j) { // swap - int temp = array[i]; - array[i] = array[j]; - array[j] = temp; - i++; - j--; - } - } - if(low < j) { - quicksort(array, low, j); - } - if(high > i) { - quicksort(array, i, high); - } -} -int extend_table(unsigned int data[], int tbl, int end, int bit, int m1, int m2) { - for(data[tbl] <<= 1; tbl <= end; data[++tbl] <<= 1) { - if((filter(data[tbl]) ^ filter(data[tbl] | 1)) != 0) { - data[tbl] |= filter(data[tbl]) ^ bit; - update_contribution(data, tbl, m1, m2); - } else if(filter(data[tbl]) == bit) { - data[++end] = data[tbl + 1]; - data[tbl + 1] = data[tbl] | 1; - update_contribution(data, tbl, m1, m2); - tbl++; - update_contribution(data, tbl, m1, m2); - } else { - data[tbl--] = data[end--]; - } - } - return end; -} - -int old_recover( - unsigned int odd[], - int o_head, - int o_tail, - int oks, - unsigned int even[], - int e_head, - int e_tail, - int eks, - int rem, - int s, - struct Crypto1Params* p, - int first_run) { - int o, e, i; - if(rem == -1) { - for(e = e_head; e <= e_tail; ++e) { - even[e] = (even[e] << 1) ^ evenparity32(even[e] & LF_POLY_EVEN); - for(o = o_head; o <= o_tail; ++o, ++s) { - struct Crypto1State temp = {0, 0}; - temp.even = odd[o]; - temp.odd = even[e] ^ evenparity32(odd[o] & LF_POLY_ODD); - if(check_state(&temp, p)) { - return -1; - } - } - } - return s; - } - if(first_run == 0) { - for(i = 0; (i < 4) && (rem-- != 0); i++) { - oks >>= 1; - eks >>= 1; - o_tail = extend_table( - odd, o_head, o_tail, oks & 1, LF_POLY_EVEN << 1 | 1, LF_POLY_ODD << 1); - if(o_head > o_tail) return s; - e_tail = - extend_table(even, e_head, e_tail, eks & 1, LF_POLY_ODD, LF_POLY_EVEN << 1 | 1); - if(e_head > e_tail) return s; - } - } - first_run = 0; - quicksort(odd, o_head, o_tail); - quicksort(even, e_head, e_tail); - while(o_tail >= o_head && e_tail >= e_head) { - if(((odd[o_tail] ^ even[e_tail]) >> 24) == 0) { - o_tail = binsearch(odd, o_head, o = o_tail); - e_tail = binsearch(even, e_head, e = e_tail); - s = old_recover(odd, o_tail--, o, oks, even, e_tail--, e, eks, rem, s, p, first_run); - if(s == -1) { - break; - } - } else if((odd[o_tail] ^ 0x80000000) > (even[e_tail] ^ 0x80000000)) { - o_tail = binsearch(odd, o_head, o_tail) - 1; - } else { - e_tail = binsearch(even, e_head, e_tail) - 1; - } - } - return s; -} - -static inline int sync_state(ProgramState* program_state) { - int ts = furi_hal_rtc_get_timestamp(); - program_state->eta_round = program_state->eta_round - (ts - program_state->eta_timestamp); - program_state->eta_total = program_state->eta_total - (ts - program_state->eta_timestamp); - program_state->eta_timestamp = ts; - if(program_state->close_thread_please) { - return 1; - } - return 0; -} - -int calculate_msb_tables( - int oks, - int eks, - int msb_round, - struct Crypto1Params* p, - unsigned int* states_buffer, - struct Msb* odd_msbs, - struct Msb* even_msbs, - unsigned int* temp_states_odd, - unsigned int* temp_states_even, - ProgramState* program_state) { - //FURI_LOG_I(TAG, "MSB GO %i", msb_iter); // DEBUG - unsigned int msb_head = (MSB_LIMIT * msb_round); // msb_iter ranges from 0 to (256/MSB_LIMIT)-1 - unsigned int msb_tail = (MSB_LIMIT * (msb_round + 1)); - int states_tail = 0, tail = 0; - int i = 0, j = 0, semi_state = 0, found = 0; - unsigned int msb = 0; - // TODO: Why is this necessary? - memset(odd_msbs, 0, MSB_LIMIT * sizeof(struct Msb)); - memset(even_msbs, 0, MSB_LIMIT * sizeof(struct Msb)); - - for(semi_state = 1 << 20; semi_state >= 0; semi_state--) { - if(semi_state % 32768 == 0) { - if(sync_state(program_state) == 1) { - return 0; - } - } - - if(filter(semi_state) == (oks & 1)) { //-V547 - states_buffer[0] = semi_state; - states_tail = state_loop(states_buffer, oks, CONST_M1_1, CONST_M2_1); - - for(i = states_tail; i >= 0; i--) { - msb = states_buffer[i] >> 24; - if((msb >= msb_head) && (msb < msb_tail)) { - found = 0; - for(j = 0; j < odd_msbs[msb - msb_head].tail - 1; j++) { - if(odd_msbs[msb - msb_head].states[j] == states_buffer[i]) { - found = 1; - break; - } - } - - if(!found) { - tail = odd_msbs[msb - msb_head].tail++; - odd_msbs[msb - msb_head].states[tail] = states_buffer[i]; - } - } - } - } - - if(filter(semi_state) == (eks & 1)) { //-V547 - states_buffer[0] = semi_state; - states_tail = state_loop(states_buffer, eks, CONST_M1_2, CONST_M2_2); - - for(i = 0; i <= states_tail; i++) { - msb = states_buffer[i] >> 24; - if((msb >= msb_head) && (msb < msb_tail)) { - found = 0; - - for(j = 0; j < even_msbs[msb - msb_head].tail; j++) { - if(even_msbs[msb - msb_head].states[j] == states_buffer[i]) { - found = 1; - break; - } - } - - if(!found) { - tail = even_msbs[msb - msb_head].tail++; - even_msbs[msb - msb_head].states[tail] = states_buffer[i]; - } - } - } - } - } - - oks >>= 12; - eks >>= 12; - - for(i = 0; i < MSB_LIMIT; i++) { - if(sync_state(program_state) == 1) { - return 0; - } - // TODO: Why is this necessary? - memset(temp_states_even, 0, sizeof(unsigned int) * (1280)); - memset(temp_states_odd, 0, sizeof(unsigned int) * (1280)); - memcpy(temp_states_odd, odd_msbs[i].states, odd_msbs[i].tail * sizeof(unsigned int)); - memcpy(temp_states_even, even_msbs[i].states, even_msbs[i].tail * sizeof(unsigned int)); - int res = old_recover( - temp_states_odd, - 0, - odd_msbs[i].tail, - oks, - temp_states_even, - 0, - even_msbs[i].tail, - eks, - 3, - 0, - p, - 1); - if(res == -1) { - return 1; - } - //odd_msbs[i].tail = 0; - //even_msbs[i].tail = 0; - } - - return 0; -} - -bool recover(struct Crypto1Params* p, int ks2, ProgramState* program_state) { - bool found = false; - unsigned int* states_buffer = malloc(sizeof(unsigned int) * (2 << 9)); - struct Msb* odd_msbs = (struct Msb*)malloc(MSB_LIMIT * sizeof(struct Msb)); - struct Msb* even_msbs = (struct Msb*)malloc(MSB_LIMIT * sizeof(struct Msb)); - unsigned int* temp_states_odd = malloc(sizeof(unsigned int) * (1280)); - unsigned int* temp_states_even = malloc(sizeof(unsigned int) * (1280)); - int oks = 0, eks = 0; - int i = 0, msb = 0; - for(i = 31; i >= 0; i -= 2) { - oks = oks << 1 | BEBIT(ks2, i); - } - for(i = 30; i >= 0; i -= 2) { - eks = eks << 1 | BEBIT(ks2, i); - } - int bench_start = furi_hal_rtc_get_timestamp(); - program_state->eta_total = eta_total_time; - program_state->eta_timestamp = bench_start; - for(msb = 0; msb <= ((256 / MSB_LIMIT) - 1); msb++) { - program_state->search = msb; - program_state->eta_round = eta_round_time; - program_state->eta_total = eta_total_time - (eta_round_time * msb); - if(calculate_msb_tables( - oks, - eks, - msb, - p, - states_buffer, - odd_msbs, - even_msbs, - temp_states_odd, - temp_states_even, - program_state)) { - int bench_stop = furi_hal_rtc_get_timestamp(); - FURI_LOG_I(TAG, "Cracked in %i seconds", bench_stop - bench_start); - found = true; - break; - } - if(program_state->close_thread_please) { - break; - } - } - free(states_buffer); - free(odd_msbs); - free(even_msbs); - free(temp_states_odd); - free(temp_states_even); - return found; -} - -bool napi_mf_classic_dict_check_presence(MfClassicDictType dict_type) { - Storage* storage = furi_record_open(RECORD_STORAGE); - - bool dict_present = false; - if(dict_type == MfClassicDictTypeSystem) { - dict_present = storage_common_stat(storage, MF_CLASSIC_DICT_FLIPPER_PATH, NULL) == FSE_OK; - } else if(dict_type == MfClassicDictTypeUser) { - dict_present = storage_common_stat(storage, MF_CLASSIC_DICT_USER_PATH, NULL) == FSE_OK; - } - - furi_record_close(RECORD_STORAGE); - - return dict_present; -} - -MfClassicDict* napi_mf_classic_dict_alloc(MfClassicDictType dict_type) { - MfClassicDict* dict = malloc(sizeof(MfClassicDict)); - Storage* storage = furi_record_open(RECORD_STORAGE); - dict->stream = buffered_file_stream_alloc(storage); - furi_record_close(RECORD_STORAGE); - - bool dict_loaded = false; - do { - if(dict_type == MfClassicDictTypeSystem) { - if(!buffered_file_stream_open( - dict->stream, - MF_CLASSIC_DICT_FLIPPER_PATH, - FSAM_READ_WRITE, - FSOM_OPEN_EXISTING)) { - buffered_file_stream_close(dict->stream); - break; - } - } else if(dict_type == MfClassicDictTypeUser) { - if(!buffered_file_stream_open( - dict->stream, MF_CLASSIC_DICT_USER_PATH, FSAM_READ_WRITE, FSOM_OPEN_ALWAYS)) { - buffered_file_stream_close(dict->stream); - break; - } - } - - // Check for newline ending - if(!stream_eof(dict->stream)) { - if(!stream_seek(dict->stream, -1, StreamOffsetFromEnd)) break; - uint8_t last_char = 0; - if(stream_read(dict->stream, &last_char, 1) != 1) break; - if(last_char != '\n') { - FURI_LOG_D(TAG, "Adding new line ending"); - if(stream_write_char(dict->stream, '\n') != 1) break; - } - if(!stream_rewind(dict->stream)) break; - } - - // Read total amount of keys - FuriString* next_line; - next_line = furi_string_alloc(); - while(true) { - if(!stream_read_line(dict->stream, next_line)) { - FURI_LOG_T(TAG, "No keys left in dict"); - break; - } - FURI_LOG_T( - TAG, - "Read line: %s, len: %zu", - furi_string_get_cstr(next_line), - furi_string_size(next_line)); - if(furi_string_get_char(next_line, 0) == '#') continue; - if(furi_string_size(next_line) != NFC_MF_CLASSIC_KEY_LEN) continue; - dict->total_keys++; - } - furi_string_free(next_line); - stream_rewind(dict->stream); - - dict_loaded = true; - FURI_LOG_I(TAG, "Loaded dictionary with %lu keys", dict->total_keys); - } while(false); - - if(!dict_loaded) { - buffered_file_stream_close(dict->stream); - free(dict); - dict = NULL; - } - - return dict; -} - -bool napi_mf_classic_dict_add_key_str(MfClassicDict* dict, FuriString* key) { - furi_assert(dict); - furi_assert(dict->stream); - FURI_LOG_I(TAG, "Saving key: %s", furi_string_get_cstr(key)); - - furi_string_cat_printf(key, "\n"); - - bool key_added = false; - do { - if(!stream_seek(dict->stream, 0, StreamOffsetFromEnd)) break; - if(!stream_insert_string(dict->stream, key)) break; - dict->total_keys++; - key_added = true; - } while(false); - - furi_string_left(key, 12); - return key_added; -} - -void napi_mf_classic_dict_free(MfClassicDict* dict) { - furi_assert(dict); - furi_assert(dict->stream); - - buffered_file_stream_close(dict->stream); - stream_free(dict->stream); - free(dict); -} - -static void napi_mf_classic_dict_int_to_str(uint8_t* key_int, FuriString* key_str) { - furi_string_reset(key_str); - for(size_t i = 0; i < 6; i++) { - furi_string_cat_printf(key_str, "%02X", key_int[i]); - } -} - -static void napi_mf_classic_dict_str_to_int(FuriString* key_str, uint64_t* key_int) { - uint8_t key_byte_tmp; - - *key_int = 0ULL; - for(uint8_t i = 0; i < 12; i += 2) { - args_char_to_hex( - furi_string_get_char(key_str, i), furi_string_get_char(key_str, i + 1), &key_byte_tmp); - *key_int |= (uint64_t)key_byte_tmp << (8 * (5 - i / 2)); - } -} - -uint32_t napi_mf_classic_dict_get_total_keys(MfClassicDict* dict) { - furi_assert(dict); - - return dict->total_keys; -} - -bool napi_mf_classic_dict_rewind(MfClassicDict* dict) { - furi_assert(dict); - furi_assert(dict->stream); - - return stream_rewind(dict->stream); -} - -bool napi_mf_classic_dict_get_next_key_str(MfClassicDict* dict, FuriString* key) { - furi_assert(dict); - furi_assert(dict->stream); - - bool key_read = false; - furi_string_reset(key); - while(!key_read) { - if(!stream_read_line(dict->stream, key)) break; - if(furi_string_get_char(key, 0) == '#') continue; - if(furi_string_size(key) != NFC_MF_CLASSIC_KEY_LEN) continue; - furi_string_left(key, 12); - key_read = true; - } - - return key_read; -} - -bool napi_mf_classic_dict_get_next_key(MfClassicDict* dict, uint64_t* key) { - furi_assert(dict); - furi_assert(dict->stream); - - FuriString* temp_key; - temp_key = furi_string_alloc(); - bool key_read = napi_mf_classic_dict_get_next_key_str(dict, temp_key); - if(key_read) { - napi_mf_classic_dict_str_to_int(temp_key, key); - } - furi_string_free(temp_key); - return key_read; -} - -bool napi_mf_classic_dict_is_key_present_str(MfClassicDict* dict, FuriString* key) { - furi_assert(dict); - furi_assert(dict->stream); - - FuriString* next_line; - next_line = furi_string_alloc(); - - bool key_found = false; - stream_rewind(dict->stream); - while(!key_found) { //-V654 - if(!stream_read_line(dict->stream, next_line)) break; - if(furi_string_get_char(next_line, 0) == '#') continue; - if(furi_string_size(next_line) != NFC_MF_CLASSIC_KEY_LEN) continue; - furi_string_left(next_line, 12); - if(!furi_string_equal(key, next_line)) continue; - key_found = true; - } - - furi_string_free(next_line); - return key_found; -} - -bool napi_mf_classic_dict_is_key_present(MfClassicDict* dict, uint8_t* key) { - FuriString* temp_key; - - temp_key = furi_string_alloc(); - napi_mf_classic_dict_int_to_str(key, temp_key); - bool key_found = napi_mf_classic_dict_is_key_present_str(dict, temp_key); - furi_string_free(temp_key); - return key_found; -} - -bool napi_key_already_found_for_nonce( - MfClassicDict* dict, - uint32_t uid_xor_nt1, - uint32_t nr1_enc, - uint32_t p64b, - uint32_t ar1_enc) { - bool found = false; - uint64_t k = 0; - napi_mf_classic_dict_rewind(dict); - while(napi_mf_classic_dict_get_next_key(dict, &k)) { - struct Crypto1State temp = {0, 0}; - int i; - for(i = 0; i < 24; i++) { - (&temp)->odd |= (BIT(k, 2 * i + 1) << (i ^ 3)); - (&temp)->even |= (BIT(k, 2 * i) << (i ^ 3)); - } - crypt_word_noret(&temp, uid_xor_nt1, 0); - crypt_word_noret(&temp, nr1_enc, 1); - if(ar1_enc == (crypt_word(&temp) ^ p64b)) { - found = true; - break; - } - } - return found; -} - -bool napi_mf_classic_nonces_check_presence() { - Storage* storage = furi_record_open(RECORD_STORAGE); - - bool nonces_present = storage_common_stat(storage, MF_CLASSIC_NONCE_PATH, NULL) == FSE_OK; - - furi_record_close(RECORD_STORAGE); - - return nonces_present; -} - -MfClassicNonceArray* napi_mf_classic_nonce_array_alloc( - MfClassicDict* system_dict, - bool system_dict_exists, - MfClassicDict* user_dict, - ProgramState* program_state) { - MfClassicNonceArray* nonce_array = malloc(sizeof(MfClassicNonceArray)); - MfClassicNonce* remaining_nonce_array_init = malloc(sizeof(MfClassicNonce) * 1); - nonce_array->remaining_nonce_array = remaining_nonce_array_init; - Storage* storage = furi_record_open(RECORD_STORAGE); - nonce_array->stream = buffered_file_stream_alloc(storage); - furi_record_close(RECORD_STORAGE); - - bool array_loaded = false; - do { - // https://github.com/flipperdevices/flipperzero-firmware/blob/5134f44c09d39344a8747655c0d59864bb574b96/applications/services/storage/filesystem_api_defines.h#L8-L22 - if(!buffered_file_stream_open( - nonce_array->stream, MF_CLASSIC_NONCE_PATH, FSAM_READ_WRITE, FSOM_OPEN_EXISTING)) { - buffered_file_stream_close(nonce_array->stream); - break; - } - - // Check for newline ending - if(!stream_eof(nonce_array->stream)) { - if(!stream_seek(nonce_array->stream, -1, StreamOffsetFromEnd)) break; - uint8_t last_char = 0; - if(stream_read(nonce_array->stream, &last_char, 1) != 1) break; - if(last_char != '\n') { - FURI_LOG_D(TAG, "Adding new line ending"); - if(stream_write_char(nonce_array->stream, '\n') != 1) break; - } - if(!stream_rewind(nonce_array->stream)) break; - } - - // Read total amount of nonces - FuriString* next_line; - next_line = furi_string_alloc(); - while(!(program_state->close_thread_please)) { - if(!stream_read_line(nonce_array->stream, next_line)) { - FURI_LOG_T(TAG, "No nonces left"); - break; - } - FURI_LOG_T( - TAG, - "Read line: %s, len: %zu", - furi_string_get_cstr(next_line), - furi_string_size(next_line)); - if(!furi_string_start_with_str(next_line, "Sec")) continue; - const char* next_line_cstr = furi_string_get_cstr(next_line); - MfClassicNonce res = {0}; - int i = 0; - char* endptr; - for(i = 0; i <= 17; i++) { - if(i != 0) { - next_line_cstr = strchr(next_line_cstr, ' '); - if(next_line_cstr) { - next_line_cstr++; - } else { - break; - } - } - unsigned long value = strtoul(next_line_cstr, &endptr, 16); - switch(i) { - case 5: - res.uid = value; - break; - case 7: - res.nt0 = value; - break; - case 9: - res.nr0_enc = value; - break; - case 11: - res.ar0_enc = value; - break; - case 13: - res.nt1 = value; - break; - case 15: - res.nr1_enc = value; - break; - case 17: - res.ar1_enc = value; - break; - default: - break; // Do nothing - } - next_line_cstr = endptr; - } - (program_state->total)++; - uint32_t p64b = prng_successor(res.nt1, 64); - if((system_dict_exists && - napi_key_already_found_for_nonce( - system_dict, res.uid ^ res.nt1, res.nr1_enc, p64b, res.ar1_enc)) || - (napi_key_already_found_for_nonce( - user_dict, res.uid ^ res.nt1, res.nr1_enc, p64b, res.ar1_enc))) { - (program_state->cracked)++; - (program_state->num_completed)++; - continue; - } - FURI_LOG_I(TAG, "No key found for %8lx %8lx", res.uid, res.ar1_enc); - // TODO: Refactor - nonce_array->remaining_nonce_array = realloc( //-V701 - nonce_array->remaining_nonce_array, - sizeof(MfClassicNonce) * ((nonce_array->remaining_nonces) + 1)); - nonce_array->remaining_nonces++; - nonce_array->remaining_nonce_array[(nonce_array->remaining_nonces) - 1] = res; - nonce_array->total_nonces++; - } - furi_string_free(next_line); - buffered_file_stream_close(nonce_array->stream); - - array_loaded = true; - FURI_LOG_I(TAG, "Loaded %lu nonces", nonce_array->total_nonces); - } while(false); - - if(!array_loaded) { - free(nonce_array); - nonce_array = NULL; - } - - return nonce_array; -} - -void napi_mf_classic_nonce_array_free(MfClassicNonceArray* nonce_array) { - furi_assert(nonce_array); - furi_assert(nonce_array->stream); - - buffered_file_stream_close(nonce_array->stream); - stream_free(nonce_array->stream); - free(nonce_array); -} - -static void finished_beep() { - // Beep to indicate completion - NotificationApp* notification = furi_record_open("notification"); - notification_message(notification, &sequence_audiovisual_alert); - notification_message(notification, &sequence_display_backlight_on); - furi_record_close("notification"); -} - -void mfkey32(ProgramState* program_state) { - uint64_t found_key; // recovered key - size_t keyarray_size = 0; - uint64_t* keyarray = malloc(sizeof(uint64_t) * 1); - uint32_t i = 0, j = 0; - // Check for nonces - if(!napi_mf_classic_nonces_check_presence()) { - program_state->err = MissingNonces; - program_state->mfkey_state = Error; - free(keyarray); - return; - } - // Read dictionaries (optional) - MfClassicDict* system_dict = {0}; - bool system_dict_exists = napi_mf_classic_dict_check_presence(MfClassicDictTypeSystem); - MfClassicDict* user_dict = {0}; - bool user_dict_exists = napi_mf_classic_dict_check_presence(MfClassicDictTypeUser); - uint32_t total_dict_keys = 0; - if(system_dict_exists) { - system_dict = napi_mf_classic_dict_alloc(MfClassicDictTypeSystem); - total_dict_keys += napi_mf_classic_dict_get_total_keys(system_dict); - } - user_dict = napi_mf_classic_dict_alloc(MfClassicDictTypeUser); - if(user_dict_exists) { - total_dict_keys += napi_mf_classic_dict_get_total_keys(user_dict); - } - user_dict_exists = true; - program_state->dict_count = total_dict_keys; - program_state->mfkey_state = DictionaryAttack; - // Read nonces - MfClassicNonceArray* nonce_arr; - nonce_arr = napi_mf_classic_nonce_array_alloc( - system_dict, system_dict_exists, user_dict, program_state); - if(system_dict_exists) { - napi_mf_classic_dict_free(system_dict); - } - if(nonce_arr->total_nonces == 0) { - // Nothing to crack - program_state->err = ZeroNonces; - program_state->mfkey_state = Error; - napi_mf_classic_nonce_array_free(nonce_arr); - napi_mf_classic_dict_free(user_dict); - free(keyarray); - return; - } - if(memmgr_get_free_heap() < MIN_RAM) { - // System has less than the guaranteed amount of RAM (140 KB) - adjust some parameters to run anyway at half speed - eta_round_time *= 2; - eta_total_time *= 2; - MSB_LIMIT /= 2; - } - program_state->mfkey_state = MfkeyAttack; - // TODO: Work backwards on this array and free memory - for(i = 0; i < nonce_arr->total_nonces; i++) { - MfClassicNonce next_nonce = nonce_arr->remaining_nonce_array[i]; - uint32_t p64 = prng_successor(next_nonce.nt0, 64); - uint32_t p64b = prng_successor(next_nonce.nt1, 64); - if(key_already_found_for_nonce( - keyarray, - keyarray_size, - next_nonce.uid ^ next_nonce.nt1, - next_nonce.nr1_enc, - p64b, - next_nonce.ar1_enc)) { - nonce_arr->remaining_nonces--; - (program_state->cracked)++; - (program_state->num_completed)++; - continue; - } - FURI_LOG_I(TAG, "Cracking %8lx %8lx", next_nonce.uid, next_nonce.ar1_enc); - struct Crypto1Params p = { - 0, - next_nonce.nr0_enc, - next_nonce.uid ^ next_nonce.nt0, - next_nonce.uid ^ next_nonce.nt1, - next_nonce.nr1_enc, - p64b, - next_nonce.ar1_enc}; - if(!recover(&p, next_nonce.ar0_enc ^ p64, program_state)) { - if(program_state->close_thread_please) { - break; - } - // No key found in recover() - (program_state->num_completed)++; - continue; - } - (program_state->cracked)++; - (program_state->num_completed)++; - found_key = p.key; - bool already_found = false; - for(j = 0; j < keyarray_size; j++) { - if(keyarray[j] == found_key) { - already_found = true; - break; - } - } - if(already_found == false) { - // New key - keyarray = realloc(keyarray, sizeof(uint64_t) * (keyarray_size + 1)); //-V701 - keyarray_size += 1; - keyarray[keyarray_size - 1] = found_key; - (program_state->unique_cracked)++; - } - } - // TODO: Update display to show all keys were found - // TODO: Prepend found key(s) to user dictionary file - //FURI_LOG_I(TAG, "Unique keys found:"); - for(i = 0; i < keyarray_size; i++) { - //FURI_LOG_I(TAG, "%012" PRIx64, keyarray[i]); - FuriString* temp_key = furi_string_alloc(); - furi_string_cat_printf(temp_key, "%012" PRIX64, keyarray[i]); - napi_mf_classic_dict_add_key_str(user_dict, temp_key); - furi_string_free(temp_key); - } - if(keyarray_size > 0) { - // TODO: Should we use DolphinDeedNfcMfcAdd? - dolphin_deed(DolphinDeedNfcMfcAdd); - } - napi_mf_classic_nonce_array_free(nonce_arr); - napi_mf_classic_dict_free(user_dict); - free(keyarray); - //FURI_LOG_I(TAG, "mfkey32 function completed normally"); // DEBUG - program_state->mfkey_state = Complete; - // No need to alert the user if they asked it to stop - if(!(program_state->close_thread_please)) { - finished_beep(); - } - return; -} - -// Screen is 128x64 px -static void render_callback(Canvas* const canvas, void* ctx) { - furi_assert(ctx); - ProgramState* program_state = ctx; - furi_mutex_acquire(program_state->mutex, FuriWaitForever); - char draw_str[44] = {}; - canvas_clear(canvas); - canvas_draw_frame(canvas, 0, 0, 128, 64); - canvas_draw_frame(canvas, 0, 15, 128, 64); - canvas_set_font(canvas, FontPrimary); - canvas_draw_str_aligned(canvas, 5, 4, AlignLeft, AlignTop, "Mfkey32"); - canvas_draw_icon(canvas, 114, 4, &I_mfkey); - if(program_state->is_thread_running && program_state->mfkey_state == MfkeyAttack) { - float eta_round = (float)1 - ((float)program_state->eta_round / (float)eta_round_time); - float eta_total = (float)1 - ((float)program_state->eta_total / (float)eta_total_time); - float progress = (float)program_state->num_completed / (float)program_state->total; - if(eta_round < 0) { - // Round ETA miscalculated - eta_round = 1; - program_state->eta_round = 0; - } - if(eta_total < 0) { - // Total ETA miscalculated - eta_total = 1; - program_state->eta_total = 0; - } - canvas_set_font(canvas, FontSecondary); - snprintf( - draw_str, - sizeof(draw_str), - "Cracking: %d/%d - in prog.", - program_state->num_completed, - program_state->total); - elements_progress_bar_with_text(canvas, 5, 18, 118, progress, draw_str); - snprintf( - draw_str, - sizeof(draw_str), - "Round: %d/%d - ETA %02d Sec", - (program_state->search) + 1, // Zero indexed - 256 / MSB_LIMIT, - program_state->eta_round); - elements_progress_bar_with_text(canvas, 5, 31, 118, eta_round, draw_str); - snprintf(draw_str, sizeof(draw_str), "Total ETA %03d Sec", program_state->eta_total); - elements_progress_bar_with_text(canvas, 5, 44, 118, eta_total, draw_str); - } else if(program_state->is_thread_running && program_state->mfkey_state == DictionaryAttack) { - canvas_set_font(canvas, FontSecondary); - snprintf( - draw_str, sizeof(draw_str), "Dict solves: %d (in progress)", program_state->cracked); - canvas_draw_str_aligned(canvas, 10, 18, AlignLeft, AlignTop, draw_str); - snprintf(draw_str, sizeof(draw_str), "Keys in dict: %d", program_state->dict_count); - canvas_draw_str_aligned(canvas, 26, 28, AlignLeft, AlignTop, draw_str); - } else if(program_state->mfkey_state == Complete) { - // TODO: Scrollable list view to see cracked keys if user presses down - elements_progress_bar_with_text(canvas, 5, 18, 118, 1, draw_str); - canvas_set_font(canvas, FontSecondary); - snprintf(draw_str, sizeof(draw_str), "Complete"); - canvas_draw_str_aligned(canvas, 40, 31, AlignLeft, AlignTop, draw_str); - snprintf( - draw_str, - sizeof(draw_str), - "Keys added to user dict: %d", - program_state->unique_cracked); - canvas_draw_str_aligned(canvas, 10, 41, AlignLeft, AlignTop, draw_str); - } else if(program_state->mfkey_state == Ready) { - canvas_set_font(canvas, FontSecondary); - canvas_draw_str_aligned(canvas, 50, 30, AlignLeft, AlignTop, "Ready"); - elements_button_center(canvas, "Start"); - elements_button_right(canvas, "Help"); - } else if(program_state->mfkey_state == Help) { - canvas_set_font(canvas, FontSecondary); - canvas_draw_str_aligned(canvas, 7, 20, AlignLeft, AlignTop, "Collect nonces using"); - canvas_draw_str_aligned(canvas, 7, 30, AlignLeft, AlignTop, "Detect Reader."); - canvas_draw_str_aligned(canvas, 7, 40, AlignLeft, AlignTop, "Developers: noproto, AG"); - canvas_draw_str_aligned(canvas, 7, 50, AlignLeft, AlignTop, "Thanks: bettse"); - } else if(program_state->mfkey_state == Error) { - canvas_draw_str_aligned(canvas, 50, 25, AlignLeft, AlignTop, "Error"); - canvas_set_font(canvas, FontSecondary); - if(program_state->err == MissingNonces) { - canvas_draw_str_aligned(canvas, 25, 36, AlignLeft, AlignTop, "No nonces found"); - } else if(program_state->err == ZeroNonces) { - canvas_draw_str_aligned(canvas, 15, 36, AlignLeft, AlignTop, "Nonces already cracked"); - } else { - // Unhandled error - } - } else { - // Unhandled program state - } - furi_mutex_release(program_state->mutex); -} - -static void input_callback(InputEvent* input_event, FuriMessageQueue* event_queue) { - furi_assert(event_queue); - - PluginEvent event = {.type = EventTypeKey, .input = *input_event}; - furi_message_queue_put(event_queue, &event, FuriWaitForever); -} - -static void mfkey32_state_init(ProgramState* program_state) { - program_state->is_thread_running = false; - program_state->mfkey_state = Ready; - program_state->cracked = 0; - program_state->unique_cracked = 0; - program_state->num_completed = 0; - program_state->total = 0; - program_state->dict_count = 0; -} - -// Entrypoint for worker thread -static int32_t mfkey32_worker_thread(void* ctx) { - ProgramState* program_state = ctx; - program_state->is_thread_running = true; - program_state->mfkey_state = Initializing; - //FURI_LOG_I(TAG, "Hello from the mfkey32 worker thread"); // DEBUG - mfkey32(program_state); - program_state->is_thread_running = false; - return 0; -} - -void start_mfkey32_thread(ProgramState* program_state) { - if(!program_state->is_thread_running) { - furi_thread_start(program_state->mfkeythread); - } -} - -int32_t mfkey32_main() { - FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(PluginEvent)); - - ProgramState* program_state = malloc(sizeof(ProgramState)); - - mfkey32_state_init(program_state); - - program_state->mutex = furi_mutex_alloc(FuriMutexTypeNormal); - if(!program_state->mutex) { - FURI_LOG_E(TAG, "cannot create mutex\r\n"); - free(program_state); - return 255; - } - - // Set system callbacks - ViewPort* view_port = view_port_alloc(); - view_port_draw_callback_set(view_port, render_callback, program_state); - view_port_input_callback_set(view_port, input_callback, event_queue); - - // Open GUI and register view_port - Gui* gui = furi_record_open(RECORD_GUI); - gui_add_view_port(gui, view_port, GuiLayerFullscreen); - - program_state->mfkeythread = furi_thread_alloc(); - furi_thread_set_name(program_state->mfkeythread, "Mfkey32 Worker"); - furi_thread_set_stack_size(program_state->mfkeythread, 2048); - furi_thread_set_context(program_state->mfkeythread, program_state); - furi_thread_set_callback(program_state->mfkeythread, mfkey32_worker_thread); - - PluginEvent event; - for(bool main_loop = true; main_loop;) { - FuriStatus event_status = furi_message_queue_get(event_queue, &event, 100); - - furi_mutex_acquire(program_state->mutex, FuriWaitForever); - - if(event_status == FuriStatusOk) { - // press events - if(event.type == EventTypeKey) { - if(event.input.type == InputTypePress) { - switch(event.input.key) { - case InputKeyUp: - break; - case InputKeyDown: - break; - case InputKeyRight: - if(!program_state->is_thread_running && - program_state->mfkey_state == Ready) { - program_state->mfkey_state = Help; - view_port_update(view_port); - } - break; - case InputKeyLeft: - break; - case InputKeyOk: - if(!program_state->is_thread_running && - program_state->mfkey_state == Ready) { - start_mfkey32_thread(program_state); - view_port_update(view_port); - } - break; - case InputKeyBack: - if(!program_state->is_thread_running && - program_state->mfkey_state == Help) { - program_state->mfkey_state = Ready; - view_port_update(view_port); - } else { - program_state->close_thread_please = true; - if(program_state->is_thread_running && program_state->mfkeythread) { - // Wait until thread is finished - furi_thread_join(program_state->mfkeythread); - } - program_state->close_thread_please = false; - main_loop = false; - } - break; - default: - break; - } - } - } - } - - view_port_update(view_port); - furi_mutex_release(program_state->mutex); - } - - furi_thread_free(program_state->mfkeythread); - view_port_enabled_set(view_port, false); - gui_remove_view_port(gui, view_port); - furi_record_close("gui"); - view_port_free(view_port); - furi_message_queue_free(event_queue); - furi_mutex_free(program_state->mutex); - free(program_state); - - return 0; -} From 88d65f7927f9c6063d83ab73622a100652690481 Mon Sep 17 00:00:00 2001 From: Paul <97420701+paul759@users.noreply.github.com> Date: Thu, 4 Apr 2024 18:46:33 +1100 Subject: [PATCH 3/5] Added Acurite 5n1 Weather Station (#193) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- weather_station/.catalog/README.md | 1 + weather_station/.catalog/changelog.md | 2 + weather_station/application.fam | 2 +- weather_station/protocols/acurite_5n1.c | 303 +++++++++++++++++++++ weather_station/protocols/acurite_5n1.h | 80 ++++++ weather_station/protocols/protocol_items.c | 2 +- weather_station/protocols/protocol_items.h | 1 + 7 files changed, 389 insertions(+), 2 deletions(-) create mode 100644 weather_station/protocols/acurite_5n1.c create mode 100644 weather_station/protocols/acurite_5n1.h diff --git a/weather_station/.catalog/README.md b/weather_station/.catalog/README.md index aebbe601..48c0a191 100644 --- a/weather_station/.catalog/README.md +++ b/weather_station/.catalog/README.md @@ -9,6 +9,7 @@ ThermoPRO-TX4 Nexus-TH GT-WT02 GT-WT03 +Acurite 5n1 Acurite 592TXR Acurite-606TX Acurite-609TXC diff --git a/weather_station/.catalog/changelog.md b/weather_station/.catalog/changelog.md index c0d80566..eadd9f71 100644 --- a/weather_station/.catalog/changelog.md +++ b/weather_station/.catalog/changelog.md @@ -1,3 +1,5 @@ +## 1.6 + - Add protocol Acurite 5n1 Weather Station ## 1.5 - Update API ## 1.4 diff --git a/weather_station/application.fam b/weather_station/application.fam index 07a5e33a..1d53d65d 100644 --- a/weather_station/application.fam +++ b/weather_station/application.fam @@ -7,7 +7,7 @@ App( requires=["gui"], stack_size=4 * 1024, fap_description="Receive weather data from a wide range of supported Sub-1GHz remote sensor", - fap_version="1.5", + fap_version="1.6", fap_icon="weather_station_10px.png", fap_category="Sub-GHz", fap_icon_assets="images", diff --git a/weather_station/protocols/acurite_5n1.c b/weather_station/protocols/acurite_5n1.c new file mode 100644 index 00000000..d62b91c9 --- /dev/null +++ b/weather_station/protocols/acurite_5n1.c @@ -0,0 +1,303 @@ +#include "acurite_5n1.h" + +#define TAG "WSProtocolAcurite_5n1" + +/* + * Help + * https://github.com/merbanan/rtl_433/blob/master/src/devices/acurite.c + * + * Acurite 5n1 Wind Speed Temperature Humidity sensor decoder + * Message Type 0x38, 8 bytes + * | Byte 0 | Byte 1 | Byte 2 | Byte 3 | Byte 4 | Byte 5 | Byte 6 | Byte 7 | + * | --------- | --------- | --------- | --------- | --------- | --------- | --------- | --------- | + * | CCII IIII | IIII IIII | pB11 1000 | p??W WWWW | pWWW TTTT | pTTT TTTT | pHHH HHHH | KKKK KKKK | + * - C: Channel 00: C, 10: B, 11: A, (01 is invalid) + * - I: Device ID (14 bits) + * - B: Battery, 1 is battery OK, 0 is battery low + * - M: Message type (6 bits), 0x38 + * - W: Wind Speed (8 bits) + * - T: Temperature Fahrenheit (11 bits), + 400 * 10 + * - H: Relative Humidity (%) (7 bits) + * - K: Checksum (8 bits) + * - p: Parity bit + * Notes: + * - Temperature + * - Encoded as Fahrenheit + 400 * 10 + * - only 11 bits needed for specified range -40 F - 158 F (-40 C to 70 C) + * + */ + +static const SubGhzBlockConst ws_protocol_acurite_5n1_const = { + .te_short = 200, + .te_long = 400, + .te_delta = 90, + .min_count_bit_for_found = 64, +}; + +struct WSProtocolDecoderAcurite_5n1 { + SubGhzProtocolDecoderBase base; + + SubGhzBlockDecoder decoder; + WSBlockGeneric generic; + + uint16_t header_count; +}; + +struct WSProtocolEncoderAcurite_5n1 { + SubGhzProtocolEncoderBase base; + + SubGhzProtocolBlockEncoder encoder; + WSBlockGeneric generic; +}; + +typedef enum { + Acurite_5n1DecoderStepReset = 0, + Acurite_5n1DecoderStepCheckPreambule, + Acurite_5n1DecoderStepSaveDuration, + Acurite_5n1DecoderStepCheckDuration, +} Acurite_5n1DecoderStep; + +const SubGhzProtocolDecoder ws_protocol_acurite_5n1_decoder = { + .alloc = ws_protocol_decoder_acurite_5n1_alloc, + .free = ws_protocol_decoder_acurite_5n1_free, + + .feed = ws_protocol_decoder_acurite_5n1_feed, + .reset = ws_protocol_decoder_acurite_5n1_reset, + + .get_hash_data = ws_protocol_decoder_acurite_5n1_get_hash_data, + .serialize = ws_protocol_decoder_acurite_5n1_serialize, + .deserialize = ws_protocol_decoder_acurite_5n1_deserialize, + .get_string = ws_protocol_decoder_acurite_5n1_get_string, +}; + +const SubGhzProtocolEncoder ws_protocol_acurite_5n1_encoder = { + .alloc = NULL, + .free = NULL, + + .deserialize = NULL, + .stop = NULL, + .yield = NULL, +}; + +const SubGhzProtocol ws_protocol_acurite_5n1 = { + .name = WS_PROTOCOL_ACURITE_5N1_NAME, + .type = SubGhzProtocolWeatherStation, + .flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_868 | + SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable, + + .decoder = &ws_protocol_acurite_5n1_decoder, + .encoder = &ws_protocol_acurite_5n1_encoder, +}; + +void* ws_protocol_decoder_acurite_5n1_alloc(SubGhzEnvironment* environment) { + UNUSED(environment); + WSProtocolDecoderAcurite_5n1* instance = malloc(sizeof(WSProtocolDecoderAcurite_5n1)); + instance->base.protocol = &ws_protocol_acurite_5n1; + instance->generic.protocol_name = instance->base.protocol->name; + return instance; +} + +void ws_protocol_decoder_acurite_5n1_free(void* context) { + furi_assert(context); + WSProtocolDecoderAcurite_5n1* instance = context; + free(instance); +} + +void ws_protocol_decoder_acurite_5n1_reset(void* context) { + furi_assert(context); + WSProtocolDecoderAcurite_5n1* instance = context; + instance->decoder.parser_step = Acurite_5n1DecoderStepReset; +} + +static bool ws_protocol_acurite_5n1_check_crc(WSProtocolDecoderAcurite_5n1* instance) { + uint8_t msg[] = { + instance->decoder.decode_data >> 56, + instance->decoder.decode_data >> 48, + instance->decoder.decode_data >> 40, + instance->decoder.decode_data >> 32, + instance->decoder.decode_data >> 24, + instance->decoder.decode_data >> 16, + instance->decoder.decode_data >> 8}; + + if((subghz_protocol_blocks_add_bytes(msg, 7) == + (uint8_t)(instance->decoder.decode_data & 0xFF)) && + (!subghz_protocol_blocks_parity_bytes(&msg[2], 5))) { + return true; + } else { + return false; + } +} + +static bool ws_protocol_acurite_5n1_check_message_type(WSProtocolDecoderAcurite_5n1* instance) { + if(((instance->decoder.decode_data >> 40) & 0x3F) == 0x38) { + return true; + } else { + return false; + } +} + +/** + * Analysis of received data + * @param instance Pointer to a WSBlockGeneric* instance + */ +static void ws_protocol_acurite_5n1_remote_controller(WSBlockGeneric* instance) { + uint8_t channel[] = {3, 0, 2, 1}; + uint8_t channel_raw = ((instance->data >> 62) & 0x03); + instance->channel = channel[channel_raw]; + instance->id = (instance->data >> 48) & 0x3FFF; + instance->battery_low = !((instance->data >> 46) & 1); + instance->humidity = (instance->data >> 8) & 0x7F; + + uint16_t temp_raw = ((instance->data >> (24 - 7)) & 0x780) | ((instance->data >> 16) & 0x7F); + instance->temp = locale_fahrenheit_to_celsius(((float)(temp_raw)-400) / 10.0f); + + instance->btn = WS_NO_BTN; +} + +void ws_protocol_decoder_acurite_5n1_feed(void* context, bool level, uint32_t duration) { + furi_assert(context); + WSProtocolDecoderAcurite_5n1* instance = context; + + switch(instance->decoder.parser_step) { + case Acurite_5n1DecoderStepReset: + if((level) && (DURATION_DIFF(duration, ws_protocol_acurite_5n1_const.te_short * 3) < + ws_protocol_acurite_5n1_const.te_delta * 2)) { + instance->decoder.parser_step = Acurite_5n1DecoderStepCheckPreambule; + instance->decoder.te_last = duration; + instance->header_count = 0; + } + break; + + case Acurite_5n1DecoderStepCheckPreambule: + if(level) { + instance->decoder.te_last = duration; + } else { + if((DURATION_DIFF( + instance->decoder.te_last, ws_protocol_acurite_5n1_const.te_short * 3) < + ws_protocol_acurite_5n1_const.te_delta * 2) && + (DURATION_DIFF(duration, ws_protocol_acurite_5n1_const.te_short * 3) < + ws_protocol_acurite_5n1_const.te_delta * 2)) { + //Found preambule + instance->header_count++; + } else if((instance->header_count > 2) && (instance->header_count < 5)) { + if((DURATION_DIFF( + instance->decoder.te_last, ws_protocol_acurite_5n1_const.te_short) < + ws_protocol_acurite_5n1_const.te_delta) && + (DURATION_DIFF(duration, ws_protocol_acurite_5n1_const.te_long) < + ws_protocol_acurite_5n1_const.te_delta)) { + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = Acurite_5n1DecoderStepSaveDuration; + } else if( + (DURATION_DIFF( + instance->decoder.te_last, ws_protocol_acurite_5n1_const.te_long) < + ws_protocol_acurite_5n1_const.te_delta) && + (DURATION_DIFF(duration, ws_protocol_acurite_5n1_const.te_short) < + ws_protocol_acurite_5n1_const.te_delta)) { + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = Acurite_5n1DecoderStepSaveDuration; + } else { + instance->decoder.parser_step = Acurite_5n1DecoderStepReset; + } + } else { + instance->decoder.parser_step = Acurite_5n1DecoderStepReset; + } + } + break; + + case Acurite_5n1DecoderStepSaveDuration: + if(level) { + instance->decoder.te_last = duration; + instance->decoder.parser_step = Acurite_5n1DecoderStepCheckDuration; + } else { + instance->decoder.parser_step = Acurite_5n1DecoderStepReset; + } + break; + + case Acurite_5n1DecoderStepCheckDuration: + if(!level) { + if(duration >= ((uint32_t)ws_protocol_acurite_5n1_const.te_short * 5)) { + if((instance->decoder.decode_count_bit == + ws_protocol_acurite_5n1_const.min_count_bit_for_found) && + ws_protocol_acurite_5n1_check_crc(instance) && + ws_protocol_acurite_5n1_check_message_type(instance)) { + instance->generic.data = instance->decoder.decode_data; + instance->generic.data_count_bit = instance->decoder.decode_count_bit; + ws_protocol_acurite_5n1_remote_controller(&instance->generic); + if(instance->base.callback) + instance->base.callback(&instance->base, instance->base.context); + } + instance->decoder.decode_data = 0; + instance->decoder.decode_count_bit = 0; + instance->decoder.parser_step = Acurite_5n1DecoderStepReset; + break; + } else if( + (DURATION_DIFF(instance->decoder.te_last, ws_protocol_acurite_5n1_const.te_short) < + ws_protocol_acurite_5n1_const.te_delta) && + (DURATION_DIFF(duration, ws_protocol_acurite_5n1_const.te_long) < + ws_protocol_acurite_5n1_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 0); + instance->decoder.parser_step = Acurite_5n1DecoderStepSaveDuration; + } else if( + (DURATION_DIFF(instance->decoder.te_last, ws_protocol_acurite_5n1_const.te_long) < + ws_protocol_acurite_5n1_const.te_delta) && + (DURATION_DIFF(duration, ws_protocol_acurite_5n1_const.te_short) < + ws_protocol_acurite_5n1_const.te_delta)) { + subghz_protocol_blocks_add_bit(&instance->decoder, 1); + instance->decoder.parser_step = Acurite_5n1DecoderStepSaveDuration; + } else { + instance->decoder.parser_step = Acurite_5n1DecoderStepReset; + } + } else { + instance->decoder.parser_step = Acurite_5n1DecoderStepReset; + } + break; + } +} + +uint8_t ws_protocol_decoder_acurite_5n1_get_hash_data(void* context) { + furi_assert(context); + WSProtocolDecoderAcurite_5n1* instance = context; + return subghz_protocol_blocks_get_hash_data( + &instance->decoder, (instance->decoder.decode_count_bit / 8) + 1); +} + +SubGhzProtocolStatus ws_protocol_decoder_acurite_5n1_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset) { + furi_assert(context); + WSProtocolDecoderAcurite_5n1* instance = context; + return ws_block_generic_serialize(&instance->generic, flipper_format, preset); +} + +SubGhzProtocolStatus + ws_protocol_decoder_acurite_5n1_deserialize(void* context, FlipperFormat* flipper_format) { + furi_assert(context); + WSProtocolDecoderAcurite_5n1* instance = context; + return ws_block_generic_deserialize_check_count_bit( + &instance->generic, flipper_format, ws_protocol_acurite_5n1_const.min_count_bit_for_found); +} + +void ws_protocol_decoder_acurite_5n1_get_string(void* context, FuriString* output) { + furi_assert(context); + WSProtocolDecoderAcurite_5n1* instance = context; + furi_string_printf( + output, + "%s %dbit\r\n" + "Key:0x%lX%08lX\r\n" + "Sn:0x%lX Ch:%d Bat:%d\r\n" + "Temp:%3.1f C Hum:%d%%", + instance->generic.protocol_name, + instance->generic.data_count_bit, + (uint32_t)(instance->generic.data >> 32), + (uint32_t)(instance->generic.data), + instance->generic.id, + instance->generic.channel, + instance->generic.battery_low, + (double)instance->generic.temp, + instance->generic.humidity); +} diff --git a/weather_station/protocols/acurite_5n1.h b/weather_station/protocols/acurite_5n1.h new file mode 100644 index 00000000..ad3fb21a --- /dev/null +++ b/weather_station/protocols/acurite_5n1.h @@ -0,0 +1,80 @@ +#pragma once + +#include + +#include +#include +#include +#include "ws_generic.h" +#include + +#define WS_PROTOCOL_ACURITE_5N1_NAME "Acurite 5n1" + +typedef struct WSProtocolDecoderAcurite_5n1 WSProtocolDecoderAcurite_5n1; +typedef struct WSProtocolEncoderAcurite_5n1 WSProtocolEncoderAcurite_5n1; + +extern const SubGhzProtocolDecoder ws_protocol_acurite_5n1_decoder; +extern const SubGhzProtocolEncoder ws_protocol_acurite_5n1_encoder; +extern const SubGhzProtocol ws_protocol_acurite_5n1; + +/** + * Allocate WSProtocolDecoderAcurite_5n1. + * @param environment Pointer to a SubGhzEnvironment instance + * @return WSProtocolDecoderAcurite_5n1* pointer to a WSProtocolDecoderAcurite_5n1 instance + */ +void* ws_protocol_decoder_acurite_5n1_alloc(SubGhzEnvironment* environment); + +/** + * Free WSProtocolDecoderAcurite_5n1. + * @param context Pointer to a WSProtocolDecoderAcurite_5n1 instance + */ +void ws_protocol_decoder_acurite_5n1_free(void* context); + +/** + * Reset decoder WSProtocolDecoderAcurite_5n1. + * @param context Pointer to a WSProtocolDecoderAcurite_5n1 instance + */ +void ws_protocol_decoder_acurite_5n1_reset(void* context); + +/** + * Parse a raw sequence of levels and durations received from the air. + * @param context Pointer to a WSProtocolDecoderAcurite_5n1 instance + * @param level Signal level true-high false-low + * @param duration Duration of this level in, us + */ +void ws_protocol_decoder_acurite_5n1_feed(void* context, bool level, uint32_t duration); + +/** + * Getting the hash sum of the last randomly received parcel. + * @param context Pointer to a WSProtocolDecoderAcurite_5n1 instance + * @return hash Hash sum + */ +uint8_t ws_protocol_decoder_acurite_5n1_get_hash_data(void* context); + +/** + * Serialize data WSProtocolDecoderAcurite_5n1. + * @param context Pointer to a WSProtocolDecoderAcurite_5n1 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @param preset The modulation on which the signal was received, SubGhzRadioPreset + * @return status + */ +SubGhzProtocolStatus ws_protocol_decoder_acurite_5n1_serialize( + void* context, + FlipperFormat* flipper_format, + SubGhzRadioPreset* preset); + +/** + * Deserialize data WSProtocolDecoderAcurite_5n1. + * @param context Pointer to a WSProtocolDecoderAcurite_5n1 instance + * @param flipper_format Pointer to a FlipperFormat instance + * @return status + */ +SubGhzProtocolStatus + ws_protocol_decoder_acurite_5n1_deserialize(void* context, FlipperFormat* flipper_format); + +/** + * Getting a textual representation of the received data. + * @param context Pointer to a WSProtocolDecoderAcurite_5n1 instance + * @param output Resulting text + */ +void ws_protocol_decoder_acurite_5n1_get_string(void* context, FuriString* output); diff --git a/weather_station/protocols/protocol_items.c b/weather_station/protocols/protocol_items.c index 1559f808..ec762cfa 100644 --- a/weather_station/protocols/protocol_items.c +++ b/weather_station/protocols/protocol_items.c @@ -11,7 +11,7 @@ const SubGhzProtocol* weather_station_protocol_registry_items[] = { &ws_protocol_auriol_th, &ws_protocol_oregon_v1, &ws_protocol_tx_8300, &ws_protocol_wendox_w6726, &ws_protocol_auriol_ahfl, &ws_protocol_kedsum_th, - &ws_protocol_emose601x, + &ws_protocol_emose601x, &ws_protocol_acurite_5n1, }; const SubGhzProtocolRegistry weather_station_protocol_registry = { diff --git a/weather_station/protocols/protocol_items.h b/weather_station/protocols/protocol_items.h index 43de34f3..6a09c4b9 100644 --- a/weather_station/protocols/protocol_items.h +++ b/weather_station/protocols/protocol_items.h @@ -14,6 +14,7 @@ #include "oregon2.h" #include "oregon3.h" #include "acurite_592txr.h" +#include "acurite_5n1.h" #include "ambient_weather.h" #include "auriol_hg0601a.h" #include "oregon_v1.h" From 50c6e35c00d97f1467f335c72ef70efbf0b959f0 Mon Sep 17 00:00:00 2001 From: Eric Betts Date: Sun, 7 Apr 2024 23:40:27 -0700 Subject: [PATCH 4/5] Picopass: Save unknown blocks with ?? (#196) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- picopass/picopass_device.c | 85 +++++++++++++++---- picopass/picopass_device.h | 2 +- picopass/protocol/picopass_listener.c | 2 +- picopass/protocol/picopass_poller.c | 2 +- picopass/scenes/picopass_scene_card_menu.c | 3 +- picopass/scenes/picopass_scene_device_info.c | 3 +- picopass/scenes/picopass_scene_more_info.c | 8 +- .../scenes/picopass_scene_read_card_success.c | 3 +- 8 files changed, 82 insertions(+), 26 deletions(-) diff --git a/picopass/picopass_device.c b/picopass/picopass_device.c index 92bd8f38..9f28aad1 100644 --- a/picopass/picopass_device.c +++ b/picopass/picopass_device.c @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -15,6 +16,7 @@ static const uint32_t picopass_file_version = 1; const uint8_t picopass_iclass_decryptionkey[] = {0xb4, 0x21, 0x2c, 0xca, 0xb7, 0xed, 0x21, 0x0f, 0x7b, 0x93, 0xd4, 0x59, 0x39, 0xc7, 0xdd, 0x36}; +const char unknown_block[] = "?? ?? ?? ?? ?? ?? ?? ??"; PicopassDevice* picopass_device_alloc() { PicopassDevice* picopass_dev = malloc(sizeof(PicopassDevice)); @@ -169,6 +171,7 @@ static bool picopass_device_save_file( if(dev->format == PicopassDeviceSaveFormatPartial) { // Clear key that may have been set when doing key tests for legacy memset(card_data[PICOPASS_SECURE_KD_BLOCK_INDEX].data, 0, PICOPASS_BLOCK_LEN); + card_data[PICOPASS_SECURE_KD_BLOCK_INDEX].valid = false; } do { @@ -203,13 +206,21 @@ static bool picopass_device_save_file( PICOPASS_MAX_APP_LIMIT; for(size_t i = 0; i < app_limit; i++) { furi_string_printf(temp_str, "Block %d", i); - if(!flipper_format_write_hex( - file, - furi_string_get_cstr(temp_str), - card_data[i].data, - PICOPASS_BLOCK_LEN)) { - block_saved = false; - break; + if(card_data[i].valid) { + if(!flipper_format_write_hex( + file, + furi_string_get_cstr(temp_str), + card_data[i].data, + PICOPASS_BLOCK_LEN)) { + block_saved = false; + break; + } + } else { + if(!flipper_format_write_string_cstr( + file, furi_string_get_cstr(temp_str), unknown_block)) { + block_saved = false; + break; + } } } if(!block_saved) break; @@ -246,6 +257,19 @@ bool picopass_device_save(PicopassDevice* dev, const char* dev_name) { return false; } +bool picopass_hex_str_to_uint8(const char* value_str, uint8_t* value) { + furi_check(value_str); + furi_check(value); + + bool parse_success = false; + while(*value_str && value_str[1]) { + parse_success = hex_char_to_uint8(*value_str, value_str[1], value++); + if(!parse_success) break; + value_str += 3; + } + return parse_success; +} + static bool picopass_device_load_data(PicopassDevice* dev, FuriString* path, bool show_dialog) { bool parsed = false; FlipperFormat* file = flipper_format_file_alloc(dev->storage); @@ -260,26 +284,39 @@ static bool picopass_device_load_data(PicopassDevice* dev, FuriString* path, boo } do { + picopass_device_data_clear(&dev->dev_data); if(!flipper_format_file_open_existing(file, furi_string_get_cstr(path))) break; // Read and verify file header uint32_t version = 0; if(!flipper_format_read_header(file, temp_str, &version)) break; - if(furi_string_cmp_str(temp_str, picopass_file_header) || + if(!furi_string_equal_str(temp_str, picopass_file_header) || (version != picopass_file_version)) { deprecated_version = true; break; } + FuriString* block_str = furi_string_alloc(); // Parse header blocks bool block_read = true; for(size_t i = 0; i < 6; i++) { furi_string_printf(temp_str, "Block %d", i); - if(!flipper_format_read_hex( - file, furi_string_get_cstr(temp_str), card_data[i].data, PICOPASS_BLOCK_LEN)) { + if(!flipper_format_read_string(file, furi_string_get_cstr(temp_str), block_str)) { block_read = false; break; } + if(furi_string_equal_str(block_str, unknown_block)) { + FURI_LOG_D(TAG, "Block %i: %s (unknown)", i, furi_string_get_cstr(block_str)); + card_data[i].valid = false; + memset(card_data[i].data, 0, PICOPASS_BLOCK_LEN); + } else { + FURI_LOG_D(TAG, "Block %i: %s (hex)", i, furi_string_get_cstr(block_str)); + if(!picopass_hex_str_to_uint8(furi_string_get_cstr(block_str), card_data[i].data)) { + block_read = false; + break; + } + card_data[i].valid = true; + } } size_t app_limit = card_data[PICOPASS_CONFIG_BLOCK_INDEX].data[0]; @@ -287,16 +324,29 @@ static bool picopass_device_load_data(PicopassDevice* dev, FuriString* path, boo if(app_limit > PICOPASS_MAX_APP_LIMIT) app_limit = PICOPASS_MAX_APP_LIMIT; for(size_t i = 6; i < app_limit; i++) { furi_string_printf(temp_str, "Block %d", i); - if(!flipper_format_read_hex( - file, furi_string_get_cstr(temp_str), card_data[i].data, PICOPASS_BLOCK_LEN)) { + if(!flipper_format_read_string(file, furi_string_get_cstr(temp_str), block_str)) { block_read = false; break; } + if(furi_string_equal_str(block_str, unknown_block)) { + FURI_LOG_D(TAG, "Block %i: %s (unknown)", i, furi_string_get_cstr(block_str)); + card_data[i].valid = false; + memset(card_data[i].data, 0, PICOPASS_BLOCK_LEN); + } else { + FURI_LOG_D(TAG, "Block %i: %s (hex)", i, furi_string_get_cstr(block_str)); + if(!picopass_hex_str_to_uint8(furi_string_get_cstr(block_str), card_data[i].data)) { + block_read = false; + break; + } + card_data[i].valid = true; + } } if(!block_read) break; - picopass_device_parse_credential(card_data, pacs); - picopass_device_parse_wiegand(pacs->credential, pacs); + if(card_data[PICOPASS_ICLASS_PACS_CFG_BLOCK_INDEX].valid) { + picopass_device_parse_credential(card_data, pacs); + picopass_device_parse_wiegand(pacs); + } parsed = true; } while(false); @@ -371,10 +421,14 @@ void picopass_device_data_clear(PicopassDeviceData* dev_data) { memset(dev_data->card_data[i].data, 0, sizeof(dev_data->card_data[i].data)); dev_data->card_data[i].valid = false; } + + memset(dev_data->pacs.credential, 0, sizeof(dev_data->pacs.credential)); dev_data->pacs.legacy = false; dev_data->pacs.se_enabled = false; dev_data->pacs.elite_kdf = false; + dev_data->pacs.sio = false; dev_data->pacs.pin_length = 0; + dev_data->pacs.bitLength = 0; } bool picopass_device_delete(PicopassDevice* dev, bool use_load_path) { @@ -450,7 +504,8 @@ void picopass_device_parse_credential(PicopassBlock* card_data, PicopassPacs* pa pacs->sio = (card_data[10].data[0] == 0x30); // rough check } -void picopass_device_parse_wiegand(uint8_t* credential, PicopassPacs* pacs) { +void picopass_device_parse_wiegand(PicopassPacs* pacs) { + uint8_t* credential = pacs->credential; uint32_t* halves = (uint32_t*)credential; if(halves[0] == 0) { uint8_t leading0s = __builtin_clz(REVERSE_BYTES_U32(halves[1])); diff --git a/picopass/picopass_device.h b/picopass/picopass_device.h index 4b4bed36..53edcd71 100644 --- a/picopass/picopass_device.h +++ b/picopass/picopass_device.h @@ -150,5 +150,5 @@ void picopass_device_set_loading_callback( void* context); void picopass_device_parse_credential(PicopassBlock* card_data, PicopassPacs* pacs); -void picopass_device_parse_wiegand(uint8_t* credential, PicopassPacs* pacs); +void picopass_device_parse_wiegand(PicopassPacs* pacs); bool picopass_device_hid_csn(PicopassDevice* dev); diff --git a/picopass/protocol/picopass_listener.c b/picopass/protocol/picopass_listener.c index 15db3b44..1a91a9c6 100644 --- a/picopass/protocol/picopass_listener.c +++ b/picopass/protocol/picopass_listener.c @@ -378,7 +378,7 @@ PicopassListenerCommand uint8_t rmac[4] = {}; uint8_t tmac[4] = {}; const uint8_t* key = instance->data->card_data[instance->key_block_num].data; - bool no_key = picopass_is_memset(key, 0x00, PICOPASS_BLOCK_LEN); + bool no_key = !instance->data->card_data[instance->key_block_num].valid; const uint8_t* rx_data = bit_buffer_get_data(buf); if(no_key) { diff --git a/picopass/protocol/picopass_poller.c b/picopass/protocol/picopass_poller.c index ec602391..4c9b74d2 100644 --- a/picopass/protocol/picopass_poller.c +++ b/picopass/protocol/picopass_poller.c @@ -463,7 +463,7 @@ NfcCommand picopass_poller_parse_credential_handler(PicopassPoller* instance) { NfcCommand picopass_poller_parse_wiegand_handler(PicopassPoller* instance) { NfcCommand command = NfcCommandContinue; - picopass_device_parse_wiegand(instance->data->pacs.credential, &instance->data->pacs); + picopass_device_parse_wiegand(&instance->data->pacs); instance->state = PicopassPollerStateSuccess; return command; } diff --git a/picopass/scenes/picopass_scene_card_menu.c b/picopass/scenes/picopass_scene_card_menu.c index 68081c4f..6caf5588 100644 --- a/picopass/scenes/picopass_scene_card_menu.c +++ b/picopass/scenes/picopass_scene_card_menu.c @@ -28,8 +28,7 @@ void picopass_scene_card_menu_on_enter(void* context) { bool zero_config = picopass_is_memset( card_data[PICOPASS_ICLASS_PACS_CFG_BLOCK_INDEX].data, 0x00, PICOPASS_BLOCK_LEN); bool no_credential = picopass_is_memset(pacs->credential, 0x00, sizeof(pacs->credential)); - bool no_key = picopass_is_memset( - card_data[PICOPASS_SECURE_KD_BLOCK_INDEX].data, 0xFF, PICOPASS_BLOCK_LEN); + bool no_key = !card_data[PICOPASS_SECURE_KD_BLOCK_INDEX].valid; if(secured && zero_config) { submenu_add_item( diff --git a/picopass/scenes/picopass_scene_device_info.c b/picopass/scenes/picopass_scene_device_info.c index 17d66fdf..c08adfda 100644 --- a/picopass/scenes/picopass_scene_device_info.c +++ b/picopass/scenes/picopass_scene_device_info.c @@ -102,8 +102,7 @@ bool picopass_scene_device_info_on_event(void* context, SceneManagerEvent event) consumed = true; } } else if(event.type == SceneManagerEventTypeBack) { - view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewWidget); - consumed = true; + consumed = scene_manager_previous_scene(picopass->scene_manager); } return consumed; } diff --git a/picopass/scenes/picopass_scene_more_info.c b/picopass/scenes/picopass_scene_more_info.c index 4c075825..28790fd5 100644 --- a/picopass/scenes/picopass_scene_more_info.c +++ b/picopass/scenes/picopass_scene_more_info.c @@ -19,8 +19,12 @@ void picopass_scene_more_info_on_enter(void* context) { for(size_t i = 0; i < app_limit; i++) { for(size_t j = 0; j < PICOPASS_BLOCK_LEN; j += 2) { - furi_string_cat_printf( - str, "%02X%02X ", card_data[i].data[j], card_data[i].data[j + 1]); + if(card_data[i].valid) { + furi_string_cat_printf( + str, "%02X%02X ", card_data[i].data[j], card_data[i].data[j + 1]); + } else { + furi_string_cat_printf(str, "???? "); + } } } diff --git a/picopass/scenes/picopass_scene_read_card_success.c b/picopass/scenes/picopass_scene_read_card_success.c index 18e9e2d5..6c303374 100644 --- a/picopass/scenes/picopass_scene_read_card_success.c +++ b/picopass/scenes/picopass_scene_read_card_success.c @@ -133,8 +133,7 @@ void picopass_scene_read_card_success_on_enter(void* context) { furi_string_cat_printf(credential_str, " +SIO"); } - bool no_key = picopass_is_memset( - card_data[PICOPASS_SECURE_KD_BLOCK_INDEX].data, 0xFF, PICOPASS_BLOCK_LEN); + bool no_key = !card_data[PICOPASS_SECURE_KD_BLOCK_INDEX].valid; if(no_key) { furi_string_cat_printf(key_str, "No Key: used NR-MAC"); From 67ed98c1002b1df3261ec095d4836e78e420592e Mon Sep 17 00:00:00 2001 From: Eric Betts Date: Sun, 7 Apr 2024 23:42:25 -0700 Subject: [PATCH 5/5] Picopass: add subheader to loclass UI (#197) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: あく --- picopass/scenes/picopass_scene_loclass.c | 1 + picopass/views/loclass.c | 26 +++++++++++++++++++++++- picopass/views/loclass.h | 2 ++ 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/picopass/scenes/picopass_scene_loclass.c b/picopass/scenes/picopass_scene_loclass.c index 616cba05..0015bda6 100644 --- a/picopass/scenes/picopass_scene_loclass.c +++ b/picopass/scenes/picopass_scene_loclass.c @@ -36,6 +36,7 @@ void picopass_scene_loclass_on_enter(void* context) { loclass_set_callback(picopass->loclass, picopass_loclass_result_callback, picopass); loclass_set_header(picopass->loclass, "Loclass"); + loclass_set_subheader(picopass->loclass, "Hold To Reader"); picopass_blink_emulate_start(picopass); view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewLoclass); diff --git a/picopass/views/loclass.c b/picopass/views/loclass.c index 4f9da2a4..f46a9dfc 100644 --- a/picopass/views/loclass.c +++ b/picopass/views/loclass.c @@ -13,14 +13,16 @@ struct Loclass { typedef struct { FuriString* header; uint8_t num_macs; + FuriString* subheader; } LoclassViewModel; static void loclass_draw_callback(Canvas* canvas, void* model) { LoclassViewModel* m = model; char draw_str[32] = {}; - canvas_set_font(canvas, FontSecondary); + canvas_set_font(canvas, FontPrimary); canvas_draw_str_aligned(canvas, 64, 0, AlignCenter, AlignTop, furi_string_get_cstr(m->header)); + canvas_set_font(canvas, FontSecondary); if(m->num_macs == 255) { return; @@ -37,6 +39,9 @@ static void loclass_draw_callback(Canvas* canvas, void* model) { elements_progress_bar_with_text(canvas, 0, 20, 128, progress, draw_str); + canvas_draw_str_aligned( + canvas, 64, 45, AlignCenter, AlignBottom, furi_string_get_cstr(m->subheader)); + elements_button_center(canvas, "Skip"); } @@ -61,6 +66,11 @@ Loclass* loclass_alloc() { view_set_context(loclass->view, loclass); with_view_model( loclass->view, LoclassViewModel * model, { model->header = furi_string_alloc(); }, false); + with_view_model( + loclass->view, + LoclassViewModel * model, + { model->subheader = furi_string_alloc(); }, + false); return loclass; } @@ -68,6 +78,8 @@ void loclass_free(Loclass* loclass) { furi_assert(loclass); with_view_model( loclass->view, LoclassViewModel * model, { furi_string_free(model->header); }, false); + with_view_model( + loclass->view, LoclassViewModel * model, { furi_string_free(model->subheader); }, false); view_free(loclass->view); free(loclass); } @@ -80,6 +92,7 @@ void loclass_reset(Loclass* loclass) { { model->num_macs = 0; furi_string_reset(model->header); + furi_string_reset(model->subheader); }, false); } @@ -104,6 +117,17 @@ void loclass_set_header(Loclass* loclass, const char* header) { loclass->view, LoclassViewModel * model, { furi_string_set(model->header, header); }, true); } +void loclass_set_subheader(Loclass* loclass, const char* subheader) { + furi_assert(loclass); + furi_assert(subheader); + + with_view_model( + loclass->view, + LoclassViewModel * model, + { furi_string_set(model->subheader, subheader); }, + true); +} + void loclass_set_num_macs(Loclass* loclass, uint16_t num_macs) { furi_assert(loclass); with_view_model( diff --git a/picopass/views/loclass.h b/picopass/views/loclass.h index 0e39b608..fc5a49d5 100644 --- a/picopass/views/loclass.h +++ b/picopass/views/loclass.h @@ -19,4 +19,6 @@ void loclass_set_callback(Loclass* loclass, LoclassCallback callback, void* cont void loclass_set_header(Loclass* loclass, const char* header); +void loclass_set_subheader(Loclass* loclass, const char* subheader); + void loclass_set_num_macs(Loclass* loclass, uint16_t num_macs);