From 8499535aadcc965ff3e94857c77d8770f820be80 Mon Sep 17 00:00:00 2001 From: Philippe Teuwen Date: Sun, 10 Sep 2023 14:13:10 +0200 Subject: [PATCH 01/38] Clarify protocol. Disruptive changes: see below This huge commit tries to enhance several things related to the fw/cli protocol. Generally, the idea is to be verbose, explicit and reuse conventions, in order to enhance code maintainability and understandability for the other contributors. docs/protocol.md got heavily updated Many commands have been renamed for consistency. you are invited to adapt your client for easier maintenance Guidelines, also written in docs/protocol.md "New data payloads: guidelines for developers": - Now protocol data exchanged over USB or BLE are defined in netdata.h as packed structs and values are stored in Network byte order (=Big Endian) - Command-specific payloads are defined in their respective cmd_processor handler in app_cmd.c and chameleon_cmd.py - Define C `struct` for cmd/resp data greater than a single byte, use and abuse of `struct.pack`/`struct.unpack` in Python. So one can understand the payload format at a simple glimpse. - If single byte of data to return, still use a 1-byte `data`, not `status`. - Use unambiguous types such as `uint16_t`, not `int` or `enum`. Cast explicitly `int` and `enum` to `uint_t` of proper size - Use Network byte order for 16b and 32b integers - Macros `U16NTOHS`, `U32NTOHL` must be used on reception of a command payload. - Macros `U16HTONS`, `U32HTONL` must be used on creation of a response payload. - In Python, use the modifier `!` with all `struct.pack`/`struct.unpack` - Concentrate payload parsing in the handlers, avoid further parsing in their callers. This is true for the firmware and the client. - In cmd_processor handlers: don't reuse input `length`/`data` parameters for creating the response content - Avoid hardcoding offsets, use `sizeof()`, `offsetof(struct, field)` in C and `struct.calcsize()` in Python - Use the exact same command and fields names in firmware and in client, use function names matching the command names for their handlers unless there is a very good reason not to do so. This helps grepping around. Names must start with a letter, not a number, because some languages require it (e.g. `14a_scan` not possible in Python) - Respect commands order in `m_data_cmd_map`, `data_cmd.h` and `chameleon_cmd.py` definitions - Even if a command is not yet implemented in firmware or in client but a command number is allocated, add it to `data_cmd.h` and `chameleon_cmd.py` with some `FIXME: to be implemented` comment - Validate data before using it, both when receiving command data in the firmware and when receiving response data in the client. - Validate response status in client. Disruptive changes: - GET_DEVICE_CAPABILITIES: list of cmds in data are now really Big Endian Note: the initial attempt to use macros PP_HTONS were actually considering wrongly that the platform was Big Endian (BYTE_ORDER was actually undefined) while it is actually Little Endian. - GET_APP_VERSION: response is now a tuple of bytes: major|minor (previously it was in reversed order as a single uint16_t in Little Endian) - SET_SLOT_TAG_TYPE: tag_type now on 2 bytes, to prepare remapping of its enum - SET_SLOT_DATA_DEFAULT: tag_type now on 2 bytes, to prepare remapping of its enum - GET_SLOT_INFO: tag_type now on 2 bytes, to prepare remapping of its enum - GET_DEVICE_CHIP_ID: now returns its 64b ID following Network byte order (previously, bytes were in the reverse order) - GET_DEVICE_ADDRESS: now returns its 56b address following Network byte order (previously, bytes were in the reverse order). CLI does not reverse the response anymore so it displays the same value as before. - MF1_GET_DETECTION_COUNT: now returns its 32b value following Network byte order (previously Little Endian) - GET_GIT_VERSION response status is now STATUS_DEVICE_SUCCESS - GET_DEVICE_MODEL response status is now STATUS_DEVICE_SUCCESS - MF1_READ_EMU_BLOCK_DATA response status is now STATUS_DEVICE_SUCCESS - GET_DEVICE_CAPABILITIES response status is now STATUS_DEVICE_SUCCESS - HF14A_SCAN: entirely new response format, room for ATS and multiple tags - MF1_DETECT_SUPPORT response status is now HF_TAG_OK and support is indicated as bool in 1 byte of data - MF1_DETECT_PRNG response status is now HF_TAG_OK and prng_type is returned in 1 byte of data with a new enum mf1_prng_type_t == MifareClassicPrngType - MF1_DETECT_DARKSIDE response status is now HF_TAG_OK and darkside_status is returned in 1 byte of data with a new enum mf1_darkside_status_t == MifareClassicDarksideStatus - MF1_DARKSIDE_ACQUIRE response status is now HF_TAG_OK and darkside_status is returned in 1 byte of data. If OK, followed by 24 bytes as previously - MF1_GET_ANTI_COLL_DATA: in case slot does not contain anticoll data, instead of STATUS_PAR_ERR, now it returns STATUS_DEVICE_SUCCESS with empty data - MF1_SET_ANTI_COLL_DATA and MF1_GET_ANTI_COLL_DATA now use the same data format as HF14A_SCAN For clients to detect Ultra/Lite with older firmwares, one can issue the GET_APP_VERSION and urge the user to flash his device if needed. On older firmwares, it will return a status=b'\x00' and data=b'\x00\x01' while up-to-date firmwares will return status=STATUS_DEVICE_SUCCESS and data greater or equal to b'\x01\x00' (v1.0). Other changes: cf CHANGELOG, and probably a few small changes I forgot about.. TODO: - remap `tag_specific_type_t` enum to allow future tags (e.g. LF tags) without reshuffling enum and affecting users stored cards - TEST! --- CHANGELOG.md | 13 +- docs/development.md | 4 + docs/images/Makefile | 5 + docs/images/protocol-packet.png | Bin 25723 -> 8684 bytes docs/images/protocol-packet.tex | 24 + docs/protocol.md | 381 +++++- firmware/application/src/app_cmd.c | 1216 ++++++++--------- firmware/application/src/app_cmd.h | 1 - firmware/application/src/app_main.c | 1 - firmware/application/src/app_status.h | 11 - firmware/application/src/ble_main.c | 6 +- firmware/application/src/data_cmd.h | 67 +- .../application/src/rfid/nfctag/hf/nfc_mf1.c | 11 +- .../application/src/rfid/nfctag/hf/nfc_mf1.h | 20 +- .../src/rfid/reader/hf/mf1_toolbox.c | 117 +- .../src/rfid/reader/hf/mf1_toolbox.h | 39 +- .../application/src/rfid/reader/hf/rc522.h | 4 +- firmware/application/src/settings.c | 4 +- firmware/application/src/settings.h | 4 +- firmware/application/src/utils/dataframe.c | 151 +- firmware/application/src/utils/dataframe.h | 13 +- firmware/application/src/utils/netdata.h | 65 + firmware/common/hw_connect.c | 24 +- firmware/common/lwip_def.h | 142 -- software/script/chameleon_cli_unit.py | 491 ++++--- software/script/chameleon_cmd.py | 711 ++++++---- software/script/chameleon_com.py | 76 +- software/script/chameleon_cstruct.py | 113 -- software/script/chameleon_status.py | 29 +- software/script/chameleon_utils.py | 5 +- 30 files changed, 2097 insertions(+), 1651 deletions(-) create mode 100644 docs/images/Makefile create mode 100644 docs/images/protocol-packet.tex create mode 100644 firmware/application/src/utils/netdata.h delete mode 100644 firmware/common/lwip_def.h delete mode 100644 software/script/chameleon_cstruct.py diff --git a/CHANGELOG.md b/CHANGELOG.md index d3cedbcb..a6919af9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,18 @@ All notable changes to this project will be documented in this file. This project uses the changelog in accordance with [keepchangelog](http://keepachangelog.com/). Please use this to write notable changes, which is not the same as git commit log... ## [unreleased][unreleased] - - Added `hf settings blepair` command to get and set ble pairing enable state, and default disable ble pair. (@xianglin1998) + - Changed `hw detection decrypt` show progression and remove duplicate keys (@doegox) + - Changed dynamic cmd_map_init() by static cmd_map initialization (@doegox) + - Changed `hf slot list` to add clarity and colors (@doegox) + - Changed `hf mf sim` and `hf mf info` to support ATS (still to be used in actual emulation) (@doegox) + - Changed `hf mf eload` and `hf mf eread`: uploads/downloads are now 30x faster (@doegox) + - Changed CLI HF14AInfo logic merged inside HF14AScan for more consistent display of the results (@doegox) + - Added guessed type information for NXP tags, and reorganization of HF information part. (@FlUxIuS) + - Changed `hw raw` to detail status message (@doegox) + - Changed CLI to query capabilities on connect, not on every single command if device does not support get_device_capabilities (@doegox) + - Changed CLI to not instanciate ChameleonCMD on every single command (@doegox) + - Changed massively the protocol and its handlers for more consistency and easier maintenance and future dev (@doegox) + - Added `hf settings blepair` command to get and set ble pairing enable state, and default disable ble pair (@xianglin1998) - Added `hf mf info` command to get UID/SAK/ATQA from slot (@Foxushka) - Added `hw raw` to send raw command to Chameleon (@Foxushka) - Added command to fetch all available commands from Chameleon and test if Chameleon supports it (@Foxushka) diff --git a/docs/development.md b/docs/development.md index da352db2..409dbd56 100644 --- a/docs/development.md +++ b/docs/development.md @@ -345,3 +345,7 @@ Limitations: * SWO pin is shared with... SWO so when e.g. reflashing the device, garbage may appear on the monitoring terminal. * SWO pin is also shared with the blue channel of the RGB slot LEDs, so faint blue may appear briefly when logs are sent and LED might not work properly when supposed to be blue. + +# Resources + +* [nRF52840 Objective Product Specification v0.5.1](https://infocenter.nordicsemi.com/pdf/nRF52840_OPS_v0.5.1.pdf) diff --git a/docs/images/Makefile b/docs/images/Makefile new file mode 100644 index 00000000..814efe75 --- /dev/null +++ b/docs/images/Makefile @@ -0,0 +1,5 @@ +all: + pdflatex --shell-escape protocol-packet.tex + +clean: + rm *.aux *.log *.pdf diff --git a/docs/images/protocol-packet.png b/docs/images/protocol-packet.png index 98372b5e33a16b31b5565c41ba1b9d5d8eda832e..2092f14fd5575bfc0bc99b911a7ea49e9313de44 100644 GIT binary patch literal 8684 zcmbt(c|6qH|Nltorck$qAxjxch%tl+bz|(KxmiM4@}{y4veS%9B^ujEWbHPE7G%l3 z+{oxn2!*l6kbN1l3^V-RquZy?{XV{*&mZ5Paprx_dA-g#uk(B@=k-1lc>$xx!!5`S zgTZ(V^mTrR!MIQ`*bdk)oKTNw;4V|>vGdwFlXEawLF`_p!!D>T=BWR>2@Dn_34?{+ zhQZdMuFwe>%ohoRP1(a>>d7z|zh_zn_6!WhvFn1dneNus7R2-YkAs7Qlaq7j&YeFx zVBhPaf`VIA>S-kMm)*O+lN8j{1Vu!6_wVQ4$E~2Iz`2u?y0rxj_l=CyHZY)WQTcx5 z%dD(yo|~&07|8qZA+s{Gd9Jx?pi1A|Tvkab{>2L?fB&#Yk9ZFpP}kA%xpzMQus?t$c|B#ngnVISDC)aXNJ$9>I_Jy+ zdS?f2oTpibB@bs9qb5P%B5Bf0YA1JJ=-w9#VXB2KJ~DEYSBaxrTZ#I04`Ts%Wz@5? z_zb?)tIZ?pg5;!|#cO;7X#W5EUugHTWmP(1L}KA;y%K8~cM|=skCAN~R=U(ywbQI~ zh}r6_(_Tx;M!*kWeEE5*Q(>A%Ap)qXAbNo8H~x>@b{EU4MN`+IHPn z$}S$ILBe+Ql@FMjR^2tKdFydgn?(fmWDq zk%H36{4AjAE^R_OvSF^fzB+wYI+GZVi`}a|E20%VtG# z|3GmUeY#>}Y+}KP$q5o-)4yH=bIg$I*iZ0JBMNs}?VhgF%2SQ2CE4?F*jo;F=6tZh zn>pp!{I3#;#3TjE%zRfmjm5f*@>_XQAMQqbBYJD^rr{tJv$-~~ai(&4Os$5Yi!z)+ zkju!0<<;qv0pYb-0@2u}e?s>n7(Hd_b#H?|Pa)~F-=5Ng3u$O|j@#7Xu!~W25RgQ87 zdtT_yGNKuRxoevH_er=gQp2&zsE=_(eqRBP4}NvI__TXWt`oL`Dvp(9Z+oL-BpjUt zGOP$qWjKx;7pg#ab{UW48K%brXJp~@y+^CQwn(dAPRmhGY27K=D0tKW{l@uOl!k1t z1FB?w$sn65ef9NmxPdR)FWiW7e=fa6xIxJjjY|E!zjXa%|C5s*zwp%

M(e2{T3g zdmbM4=RLgQq>sLXu-O<1wXabSH=rN~nf!kEZc7RA=a|O(vT%j3IUnz^oZ}*@N659e z;a2_o=X6N)S3n3ipGIG7qmM*n5;1B@2U`k^9zZy{9@5P&Kc<6obwZ6y9cq-+r`(K5 zimLglV$kxKckk}Aw|J#6hAOusG9_rwNOXxPLtd$#L}HX6+9`F#LLN@PH;gDt>GC96Ghf1MIr*$`VIL8F3eXSO&Vyz-HTfA%RX zD=;P20mX|Ne@%nL&L$uYy%K6fX*P&?OI3giA&RCVo`sX#W)67F4O+=+xu8nMvp1eq zo@#RbbrnI5ngZJ@M>jVO;)uYa%l>`Fz0(Ns(;uha?UjQolqrcv#qjwZiG3OsW4K#_ z>J|x91)xTbw|!k9iT;HL1$Xz6u)5%-$8M_2H%=N;kfpWc&P!5o8>6eY0cJ0f<{9)R z>g!v~pFBn+ui~=uzDNXZ7b*VeyQ+xF7BVrHv?E z%--AeE2fD0V;4#EIxSIS?T5qmV>LwFeWAK6;0)pF)E8ux+3Uy?2QNB-0zfdB-JNqv zQ3778AQZeL#_XS)H;o|CnFB+1bvFUTc~ldrJ$bg-!>6Rn`0l_EiS80pP>cfQ(~z+RM|?3*<9K}KgSad68#{!R&@s6cVAo!?AckpNJ}|FQXJsG_x4#U!Q};x z!xd79b=cEZsFG&Phxb=xa2wSkHXEA>UY^CuaJzA<6Pikr-2q@u41oM-Q&p)gCrP6# zDPydsCCR0ppbvru2YX~C9dR|Mt&;^#r`Ub`4V4j3{JIDz-`t4yJJJ{kMfm{-O5F!D z%}QA#^!jkf@?2L6m?H$K|Gp%7p&YlQebb-cbYD;F00eLv5!dnzOcs?`X1pH#K>S)D zp7lcggY<^|eLvjGWa77a23NRmLc5I@;s}X;6MT5Bg2JN;*z-q7bw@30?vsZvv0143~L2 zpRLmE8;XZ!a9l+SHH{^?UCX3Ztol|=ORsh^ii?Jr2zcTtIXuR{x^vLl813!(IV8Wa z6BJmVz<~jiz-^mn1I)4!wNlO%qZ)y+%swf&M9nBKe%)Ijjh?P8H|6PflMuY}aRC9Q zv+6b%&VxA?O6c~SBS*x0MDeJtr$k#%H}gLaFyakqh$wZQ{Fgx0%sVqBmsX6NhTPFE zIIeId`y3U#a&38OWu<$x!YraCe0KH^6)_B>5UsZ9f_lbW|P$Qmf6|aHpq2zF=s-r100W{6$Rh;d2{;{E*Om` zTX!aG3cl%moeJ+>ow&@>GVY@`XIvH!JbcRG6OTj*J1BbCevkYhC4#xV8U~Pef5F@Q-M2Y`e?pl04 z)%5WRcq07zJNJoVM=7$0%4PjL$zLT-CC7-Z&oTzE+Nbv*sa}KfHO=ap5r&Y3_Zg{T zDqF~`>x6u_lpEvCURCBAy&LO{&WI&j$t-+bYj-s(K!>D(_%rZOBQu9lvnpgjxgV|E z%ff$}ExlrJ0X4XFDof@orzDL=+6Ori6-Qa`> z5ECa<`6o9tgx#@CgubBB|31xkZgxKmTuTIC3xDWy!CcpAIFq|xBIe)hOpG0_8vYmo zgP4APwpaU?P(S;B)czYij+klo^0P>7jw=3YF1mIZlM%^I0x3evBM8>LmDU?f-%8d& z*%I=g>9=Pp-A#*em8|JD9n{+4;ncz|>x!of$<)H2IfZl*E(|ckDojbGPITG({GP8H zD>Q{K*3rIPB&55%u#({VhQHQ`?nhNRhq5&do(!(MvWc0y-K@S8kwiF%Ad?!Bc@9ri zW3E;FIrpxXZ}wY$mcwGOX9}J=H%btk3l2_gJHdb}_i;_02D!*@$*i{*rxEA&9K?#k9rP(&0(WZVm)qsw z)t)8KMcu%gFVT!g&l6ogN7aVY-#=x2WADfkO!b~5CCvEcX6`Gj9PX=~7nX$!(WNXj zSFf3^m*nrRJy9OLb>mQ8Z)=-zuI!0&bzUhj<`v%to`yCaX83LS%k=0NDy>v_9VQz19*C| zNQ#Y*@&^GE1^h?b!U9_oF`?1!0d}r%Pn#j73Brb%2Na{nK>bE)HXE8zALUvtjQeaf z;nsR^$9?YI>Z~A?g%$)#OM;?)KB3cAt;pvgaXrMv5R&3N1)k0@?+%*wU`BMhhNhv% zELR{$d=&&KDOF0m2mznO{S^sq&zVO#Xkg4{YB8EXSov%8`#Swq^pQ+5SnA;^`?a&R-jLiqdZ0|VtmDe@Us*+QQOfS0%`aE%6X-sH!{JXkv5C)Wk;DiD~ zT=)hb6n<1_tIx3g9i`npYpPHDvh38AmY2XwEaNz$ssWmMCn=)SNTn2gAI$OZe13v0 z<;S*LuWY^7^a#<>TVev1#rUh)f$m;2aV-b(@zBm~5M=thH|s_BjqiZTm~9*S4aJRH zS|-I;Hn}ZIK}WPSl^)N+km@I?m#Uk&{l=f{ zXKAlG&U^RW8S19=vW@3HP@HLqzINdu5weFVLkNzuDD-mO-JsV&w4u4tM!JO3MI%zX zysh2SZmTWG9B}bOw@y0}kunn#g6g0brQ=8id%*X#Z8rClwxp?R{!2nj81Ta<5`eJW zbQ|^H>t?%|5W;c4W;}6lrt|JzP(M414b?F?*(Drj93#_aAPpyzaxlq9?cFF!?;7dh+vO z5!}wL^3UwSyXyPx&eUT)?cIlQe+Fp;le;wDAsZ%Ivx zF6H{w9`wcB#eEZv);l(UtsPirL5BeQ{M$b5{yr4hC)z(h|Be3Rq-39}&@9`BDRgRn z|ES2p32mtioNbuu(hzxTSod+%<~YuTGK8*5*ACtjdTpJbuxg_RCyYS%9okIsziu)c zYKSKE@d?7M-2pLMC!yO4=thIHnlbKB>>ezVP^2%#`78taG(k zg@bYX&Z1;ei0G3att${Eqe8{K>EAAXs+=trl+pxY2=Wu~y51j~9TJhPk81>ocyB^l zLf30;K@azIt|3~nutp+#yt1OXwt-8#j7`V=q~h!Z(^(Iu@2z8M18!?x>QkTftWcOr zGG1FGl-TRbo)fi!!qAWpV!2%CBx`{}U=WpB>t!#4Lyj_tgzQ;Y#rSi~j+^kV|=`FTuvZt)matsr&rYo+-jgn+f zVQ3M@^CYMp)*bFtKhhdISwnoAb09|d$z1?ODsTXMqQd&prKcVq4Na6Ji^i%Ksib(gsljnIAU+ebmm-_ zv=y0v5)Sj3LbGM^L&<`az?`UfX@dbnfKI_jtHVbdFe2rdvCLUm8RtO3_P0|uwS>hMC ztP0PS7A&V6f@Dxp^i@xp2QLw>zF5LUu}z|CY;N8mz~uRJJijlH?aqHj~5bq55f95?d=7(w@+QG;>n>r3|9AC&G+6j=BS0 zk5*qk$rhvoRGU>Wp@^LsdNO(wxnBQRp!$ZQ;5G@e6$MkY#+l)(Ud}JHYF}ziCAL}^ zKv0<*qD!ll;Y;MFf@G4CQm>WdR60Xo^xdf}7?^V%#nrwD5Jhgr6D2VGu`TXECWZfq zqpMj0+q51LyQKRZ|CYB_6vhlx!f?6>3`@W znf-an^{nja%KZ2@S;82HUF&S?+eO0ml$kmip;-VBKij8|77Nd{mJ&#K7dgq!+vh_5 z!mjzr<9Q)Fg|hJ9Zh3Y@>LG@&K*2@ScVo791OFA81-g{nE^H@+y!40HLQV<$7mdyH z-{}8?_P59Db6toA7VaWA*LuK(gRr%;;2If#*+1Y21z=)u*!BwSRiMIda=}R7DLUSDx)DacNd9c{xxUPu4dH*+Qbz9CTDHQ5q+*IZCcKCZ(pm;kJHi> zdvKY0Mrqn_jzB9c{4sF3A(cV@MroM6Qq`g@8`z`E%-L9oS^LAE<^y88@N15&-D~ zt%|!ePPEql*$_MN{0$UX!(}AIADN^a@(iu7TvG^k{y6W=cO{>wvaLjU#i>!wQwBkd(g*+Ps+@ z9JHM_@ZfuQ-e*bkG;+!h-^M5e((L5zwuQ1ri9`j9;hFj?wzrqeY4k#XS$3s{4Zwvr zK-%Gzv;9vwz!t^y^pi&DlBd8)=p=aT0yk>NkFDBRaP{^!3=uT- z8U?l1l}1{eV<8Z8!vJORYFm!u@bU5uk0%k|hAfS~CO0J%xNOgb0t4QA44CF~Ebc#f zG-N{uWdSJ)5?d0!bUv%~S~u4to%ZEk@Y1$UE<6H&)pRG$7%x13V*+@od|S>)VAPK= zcx5J{QzpS>~&aetX1E9KU-q*ld53CHDa2oFQKlKp#k zU^DO2B7f)Fp4bMT|d_w#uPk7@bh0#O#U96I=-R4FI5yB-)|ji#v%t5h3uZX4rp#}gly}NlYPds3-o4s76psj2V+;E$B*)p0z%a9RtD!>v9XZu_9%>#{JLOx%S;M)Q1O{I;+h%HL=PdxJr|iM9*7WXy=&64Tw~&+kjK%gE$G1b>Kcnu?{OCt0{NL#zK1oSQs+|hBR%qj)QIfP7 zlxKn4IX9|mnZS^4(wCO!GLC3y3b%Aw~{#xiiBjK4L#zfRW1RC z2_+Cy0fjY_vW1%0b#8X(_4hMdg~%iE`$DIr#Hs6TxFOy43Vjpkv{%5}D*@O#083>Y zQQg~l!GE6idkK3Q=+_1r8zirSVe(wRJmXz8y^c4AzESod7fK}QGN77G3XzffaZuQA$O3jwiOIr}z8hLv$ZaJ?1N~^D1jWEty6P1Hm(HlH z&d_^ufCury5>Bh70PM2~r2!~uK3@9;wQ;mtG`2ZG>6CZlr@?-cK2Lwm)+3h{@H%Ax zEaVduVtMfunRa>1V+6BLfZx&_DtH3HbX%x=`AUA(;d)w=6#VRAl9yj#U~u}3dw_nv zgs(!HdWBm;N=o49a5MNi*LM@mR#vI(>SW$U*Hv}Ssvx6l%dl-KXULP0?OS~{cdj1 zyX4<-NXJn0`aimVh(lp~|TZHxS#Zeht*=YHSDj#35B0rcC+j1 z|NU2RXyn2lEP|ZWrbBl9vIo?Z@X@{G1336NsXKZ*K@S)TiBdX=Jbh9TX@)|oqg0@O zStL>&iDXPXIs1pzl^kkV}?#eYc?y~0L9qH{*_+-U_{&EYn?oToEuYvyIag~IX9EB z_HE?loOQ*?hAH8q@~M{z+(!e~;T;cr^KRONKS{oLl5WQz^LPI9$A2Ed|6ft?v4b?% zZtCXJ^V`Ky>qrgnJl(w2)OJ_f^PS_B9i+mPBqSM6bGMTm-+hPFgrQ~!%}Ft_?ay~! z|Fm7^EiD&myKLz03m3Nkab>;ExvRGdeEJ_gy<@Yd|Gt;(`VF-tG&`wU#}8#4Wcsn6 zOyIgsEy=o*Vi)d{@d-UPE#7P^?s|Fe|M@SsgS|S4*JPil^YVCSL0{$TrM?gsRpoe< zHJF1%J`y@w0)kOyV9dS~f^^tuGZSlWBW? z@NB!w+Dc;ns_OPqFEy8}Jv2OHGh_K{rNu`>9B+8M;GY$;L4-5ypBYW_HlM;EKhBz-u)|)mZeWhFI3D8;{ z6EFHan^P>~u}w4M)+d3z$Cq7p>1K5+7K;qpH7S2T%<-bbajN30JGBXIahZn@)8svt zDTBN%rJi#&5z-xaTC!mm0uv3KCq+SRhG4D5IhM?Y1LmOJmU&|;Ra7ltQ#~ew&|AomiQd?%nYP{yzPapr|@i}kBo=`!^x*1E~_d#L|9m)zU~~<>c>~m zRSy>A=a5q?^*Jt@p5EFdx~k{Po#o3c-z>iUY-LyX56iwvcJpp=+0}0)-*poFxbNrK zO_fe*CaCk+2KBo({8T`%W&8Rj=x84SDr9IZm4rbgjVZM3#W&Ro00;8F3_lCs$Qs{RH%e2tlwdR(3)c~3X_tjWB6 z^)ZM;L`igYKJ21<&XIEtn(h`orK#5oHd@9nSN9fsF*z+xPmVHIxf?V04RVQS7j9c< z@^9E}r{e!fFW9cPm)@sz?WtIXrMhb{kAYl_LUfu*%S{jUOpEjER->&dnq;+V4GU9! zJ=XkMd5(j7)G7NjB7d&ybh$7_rkXo2+W6rti$dh(*R?UaW&R~G0b0&Yj?(QZ20p6C zl;U1kC3P0K9ktYP&2wJKvR~kJuk~UQuPrK<+tgz+nb{Z2EyOS_CGVBK&W`V-5SjgS z|2M;>D$zCdeHXvKyR>HJ>H7S|9qQxi&*#G1a~*=1daHt?omUn;MOQOSt_Usmhv6A| zGiXHTrzxTeqzlO-Bufm1hQLje2pKp>werN+e#`ok20q- z$>H?lqpP7x<+F&E64vBQrPYzL=m$K!{OLmGUHADDp3-n%sH8NxccbzB^~o~_BU$S$ zYfp5o`SjopPEq^DBwhFASRNq;>9)pCYoZQb z!poCo9K8}X^C3Go+jy{6BXWE#`!gasrw6{vv0d<>Gw_gj<4b*)l2vK1>K^@qsde$w z+^Onm#wwY1mh$c#`O9)K?W-$GuT?^o6VwfE?+nTw&5AYa$dildUhpeDwsP@h>7$)Y zPt~@jJlFJQJWt+IC%7tcQOsc3qI;^hB02GWhKZ}c&pLxGyVPUO=GxB~8#%F5#{?f& zO(|CQlX$$P-zS{1pI+F$SmnNb*XI;5_fPjWl~IJNI!}D1)@?Gbn(%V*PTDlIRRLdt zvp9}Swfwc9a&Kp3h_f7DL;|AZ((0Dtl%ibQBYC7wYb$ee1K;)L$2%kr8;e4 zA%fB5>}nGNVXn70)uW!Goh>L(It_j>?c!hb$*^~XFh)@Pdg+2%Z6#m z8uFA^=ZooXkF~dLZXtD=?3QG{Wj$1{7)$Rs{^{X%+V-TG5#x@$wpq5L&(J8!l|p+i zp6xx|tI`&SqF(0DuG{G)PH)%s_-J{BZwpnwM)vIzdS2tk(98OD;)goFt{vs%BvY+y zL7ja2u3=ngr%6j%uI|vOZauYCS;WCYT4I&R#GifP>)Z40@C;MiRktE`oMJANOE z(wnCeMk%I&99nsrx%M>a*&79l88q$t5W8y9CEka>N6K_W%O9O_X?0$iY0%lSGIp(q zs{hEPm#q$CxwEnq{AZunHzsQ3lT)%tqGSu2hp@tcquOpEHc{bvSZ+MLrdi1z7vaG~C>9=Q-vLCgU3+Rbd2)?o)c_r4OuQI-JQ;x%6bmS9C zR;oFd*9$0Yr*E=WGb+X?3|ekqpn3G@(bXK=4{T-mqdW#RuSA@hvwBRnrYvXsaE}-@ zB+vwx)itLWXt#V^K;zA&)?Z-uvlOx`drnP06)>RNtmQOQr^c6$wkbKN6}#;7)L}Mg z!>Znf&#!lHP3c@4AosjW|w;(c3)WfCU|>T!oNCog9m%H++lZW4pW91bz1fwInC^Jla?dm2~v+! z>UQiG*lSKcjbzgeYD(4%Vsf5j{$ZBCQXj9zXP2Lks5Z&pNtrGd8!JT3b~V(r)FCL| zW>GbilK-*&Xl7scUMW8ozaKN}A9}9aUEtvA@8aoTb|oYu2vH*tPEsP;f_mt*G`pYk z#{Fe!g#b!s3zoVM+Tvwt=@TfV#T4UsqY+leK9|A+$vD>mf0y2xr`u%=eB_yD{Ekdo zGX6|+`IgRJjh4#kLfQS5TB|%*ID0Mf@af)PC6Iz%$d)LKPdIf;_gMPnC`H*6veLp62IV%3*9(2r!y-DNc z+*MIkrY2rUJXH8l&%vn8m9hM5e6jZ-Y z)D+q8)4aTcW@5I*Os~wpBFDg+!6C35K&M&xi`V@-?RFg1=AvJ?h!FTgHfHMnmffFY z#bN`f?aoA8FSZO<5?&rJK(Wrz9a58YDt~^kKv~O4SIg@V(fQR}S4XqQbdJ>T_be#>)mu=JZ|AE4SqLC=$N zthz?J+wod&d7zaGBwCLf3KW2s+dPde3`a;ro6ypDtQ?V+Q8*c$?upv|MppAkm=7R zvIuDEvx{bk(`K~w^MkEHU%N2vc-tfa%cv*;G+fqnuUOOWSteciagQSA%9F)+2aQ~* zYRbb;#%PT-0_tpFQ;hLlY+gYC8A%$HFA>U#o zeU8YFRg^1y?L4BmXS{YlUCFNUVRq+8(nrQY_a0+R|WF_E_GM%i2 zmDMzgpw5{i7hfERylZ%L@|FeT!Ar;Mr;2RCJ<3*hb5o*^?`E#{V^vODt&Y(eS0+>S zXrfadYpN$@51&3drGCqxEiz=e>KK`-=2^DD8+D0_jd>5wUS5-nkc!B2kTsP0_V#?w zu!g3K&Jx*HDxtYnOAB6oTM<%U2kXJwk{pYRQSLTA8-Y(@mj3gF=V!}h`pKs_VF#&W9=F-;WGJGXf0MnAIpXIoUC5F7bC)wK7;?7F?g%R8>0EOC&-*Qp5FN`0J7A?msPdhT2_O^e2Y}LnZ$pGD{ z&!*8YEN0c-U8ETjS?P8uq~X5VxI=SA^cC8&w10)xnddcZqFZ|XxF@%jihgL#v@G-S zW>roAM2Sh{F>OhUH!=HMwe|K2frk^-$Xv!hZNjW%H?=MEQV_5C+BG-hIOh?yKwguV z3r;N$`}8i%COa-zSbiCIT=U-cvg0lT6?$vL1~r$c`vMbJ65sZANP;AIo^$dk60OZQ z`IAz-s%}-VMy>;|LCra*sM-i=>KXr+=%UBPu zeNU;M{5@05DYFN2|F}YHf3!}pO@VsDHp(Ns@9&C+OxBy3t-n(?uNHc?D^M@^1I56D zJI&RC(d`4<{`{dAG6&|5RbM_QX43y!Va)2`R?>&$yJ@eMAN|8|kN#1{P~PdwF*X7P zWb4j8c)XphclTxy@|4{>f4|e~&bxQseEzbLYHG^{2D0YIo1?bp`V79Vymk8HmFqXw zyDx0G>q$CtZbR+EcFCE`dKm|z)}Tq>6QYUtgo>t^4K4QF>o6Vmrki~^zD)6**Yrl9kNAf*FN1%%v&4;*E`GJ zmF}{(deW%AQGDl-|Dw4ym+OIVj8%#^1oa!pt#?G)KTgb*^xL;@AeT5)lDYwL3=&=o z*;!zq;MS{D&$1ezjnsu|axO!MWBe?3J(hnTq_m^)B=C8nl3a z$5@czxRB(O-C_V%38dw4*yi2;Me6ERM)F86T{#1?640-Ba(1L8S*N5XK|?UraAvSh zE*kW`(d~N7Brk2IM;B*?B4t9-JogoPAC~U!^w=+WQ_Ea1MhtXW!}##6+2JNo6$5SH z5R##qG+uampFCxF0o3@c?5T}^@6Kg`Q#8AGbKmogr4@)r&eN?@T^CW;u8%ludr zW3IgUTybf4ANbig`N+%X&fOnQ1I^`5#h#Sl$7}3#Y7AW8Ku5j{IF!1+dZYR{X3ZR1 zF3&iX-EDV3Ky1Fz5j>zk^ zadS#ZhB=8%hf}(cI`IYU!AP{^ZFSEeTP)+q_w~XQTb8su7v?F_oaeLzwkG0g**t#y z=+DVpA3l5lhv@vnb<+*A`MD04I{HFS1`b`-(Im_sAX{VvQZg-1ZTkCTG)!BM|Ht|8Fm$MRj!X4H2A<5T0<55GNKpr`I_ zqKFAz`g|F+A)LMkh_f8gmmmaUk9=#kd&_W~2%m{hlB(^E=m zr`{CL-?g%9hi=!G$2DUkVYf*B2F$WfBX_c`2DqG;=M`g>Mw*h3a^@?8tdh9>34 z;2fnSIRV~c42f=*c~2RE{p``BdFYKIuovLpA7(&XmYq&U zXyV*N*Vnq9TIe|1C@$dY4!0D~;tU3xk|V7{%u7VV-oB;Be8}KD-M~`o-hJfa z^8~iF6)3QLV2}lJ?WZBm4x^s-SHD3W4HXlVzbeAooIk(oFR;EVEz5d{$|)9eaLKqM zXEK{sj;*Xon)8Z%<^rgo*r1v>kV_|048BW!D-YlZ8UE6G-O(A!CInE3yIgjY-Kc&L zR+6npl61@BDEiv){K~`Pue`a-V=xqQ=9Q>R-n0S#`!f&7CBG&r#`@1Z87T|k04;;h zSDth_&A6$S@2XaoRWx!CWwj=4s4n)X!*k&v8&}IK|2`8?iDmq4d|u;0BRgzAR zzYQfMpZEVQwb$Jj{98tmZnpVb9Vh!^^l$ZS>w(t44K4roeZZX!;1`8g9yT^MA_1Dx z9~BLJk3?u#O!vz!k^jg)_wQ5S)h~#c*z!Pb<#^RpgoVNPcNHO`Yly>P+P2Dfmx9pw z(wvrV|JT3FcgnK&sZ%j4crF%2xw4~|Uqz{<_g-erMaEYaa>wX71(wi)yP zL?JT&k&cH@wcB)|^8!pL-P^fyC#Xwqn;r9ipMFippNgY}3Rv}@0B5v8^m!VkzBwc; zKGW8#?4Zk>pob^Dhq&C|-+$rRKAK>POz2jq;d0T%&@9@9%;(3iTZ2zbEB^6Y3FCEM zwr#ozU7l}t`@H+VMK*BKxl~sDM96Ru)&d@&-b3+G&x%`#?0lF(mW8(fOiJ-MhMJL& zf?g5S&e3@#K`TEPAZyF+BaO-=lIQ<&N`K6c@qlH=gN5y46{iK}s35>f zX`dt8$@Z=LYfS!eYX@`nd3P#UC>9Yqnwb_o3ej?xUJEG3+mEC+gs;L|VgwEbtlq^C zk=#@<)JRT%6gUXhx_xAnfTso`Lt-Wz7bZ)bVK#BcnTd;wPi^5L{p(R3_(5luGx0F% zY9@|yWpQR{ra`Mh5gpOv@#D;?NNB)y}#Vci~my?>$1)`_cR+#-bhk+6p!Apd^iyZGwV1Uxcn+w(y1f?NiI9ehBo z`RLiR)c8wxfgsPFJEs6**|07q$ym!t5E`T+Qm1FHrsKp{LhHXUJJ!LmDF^yW>L=h? zH^_w7KW|WJe}Da6p#Efc$)+82??+4Be-?QX5@Ebhw`&?KOhoBBId^jgz&6On_pzTB zcdIf+@a9ue7`7tb6ULcRbyUwA zH*Nso!7tSg2Zd>ChHSjIQZuAS6;>Nx5oLrf3V)luS;z{E5lICETvq;a*5eJ**dhSW zrG!&xwow~7={JD{fb%K`$z_;~GFDsxR9ZXK=06Kd?(#GXj%NIXs^_oW-*pFOifM%C8Xn85HRvEdOV1&1v}A4WcYC*k$Ed8F0}(-EGtWV918 zZv?TcrbJ!+a092cO~$nn0(9oe`|AbWJ0q?KF#&E<_PXUsH6&<+g@x4#wblb`vMx0N zvze%SutQV9DEsKi6X{{N4fcC!s)bAJN6Owu%cTty zq<5;8%Sx(AybM{^^@4{MQ31y_k`hwoJd=zXjt^afokOU8J%vy;!t~@B*BvAy9d&<_ z-Iq0^L)QHWfkn;WOj0$%2&KPhW&S(MCsHSjX$I+Xc6AXoMV<^W#bsv2oSNv?R7t)$ zQXB0vI4%F~qStGe$&#ZuIS^-C%&>*L#+YWj<717zd4ykwupJZrVR!ydo&UJ`&|FGf~@-S z4`Li@X>2gsp`B`cxba}PI_sJDeNuLc^tp$G67(z<_KJg()ll9&PrL4Tq?h8{!n-+Y z=Px>c+)97xk2_yv$+D1j`vt6?@3L#rI*^P}&JTu5DNe* z;^8}Z&DzzecF>(Vu-|mJ@k5eYQ)Y}6k*{`rr(ileLMwebCGo^IgLVvc&)YdPGQr>w zPEq8Nx83m_kJUmWSjEquM~X?zWT0}n;FxNY>|U3^D#8?@ep@c;${PSgmAPwQKv(wNah(0Cezkq)fV1Nn_3gKMDLOA|D!qIWcTlJXg`U|moHy(P_Q2_JlW`j7|-@D zJ;q+j<^g)fXCgzmCL8l(9&)VS%b#6MDO&Dp&cOG>bOb(pCc4B0JV)~y!g zUYyz&qKAQ>{GLTHLJO_AA&u#8Yt4c92XY@Yy5H-TQ5h_AwZ|%R49&x>Qf`PIQ9Ie3 zYNWfqg?efM4iCFMP07RYuy|QaC0B`L&pcnNLI)z~o&f1nI@jT<3*^%E{=S#oibQ`c z?b4sh4#~3D3(r?3In?@f`tW5zb6fFfMg;{)XSQL~+ zA}(|E*VSwjgxmO)H~BgZ@6d(FOL zQDhl1hf`p1@fl)JdFBek-0Z;jqSlWY8m^}B(*Sjr2U*wxDEl0hNi&{wAtp^48iU+J z0$T!Zfi|(|@&d$#6-t(FF#w|kJ|Da7hgXFh18o zM!pA5GE2ZY7(3cwG;UG4#X9f+ei|BCBn#f13^&F{;Y+vALmjwYf;YNIDQ&6 zE&NJM+0l#UaLq^suqsz3m8`9?i#7Fv7&#%{Mb(cPL7F{5KC%)<*LJ5l4Ldn{hhfgt zLD*{jv|kK=Lk}u0mKdn6U$SSehS8KWqzTOJ#dvR0B-KEW%)&F#@Eh#a@M~Sdn{jm{ z+N{a^3pOX&&;$o}t>4BYGXZ@fYs;1@yotMEZ;fo_p@m_fNYk#z59ER9boE)FrQ8wf z8;!3{nZMP_cdk*7P@f%bTY{OqK2^(c;=xSkuCx2vL3Y$~*2XH!*hh9QSk*6nO6$zr zcY2oXqz!6j(t#FfJCv)a#kcxFU@E>Toz*P^ItE4_1hr!X>n)ytoY8}e0mJn<4T$3q z^p9xyNY!+&-RtetXQ;3Gq9QYm>Yk{1-{W|JJ_x|dN&G&(c0+m1fvfV7W}Q=+Q($yez`{>qOV0wkh5_4DVy8_ zeJ$zF)&y4HPxl*bDDIi@68Au%YP*fCm(Zm>%HtD1JVb^ruxY)PS%} z1}C;e?g_#$0ZGo_H0|Z|;QBBK<}Z(S#;c|KZz7WZ>GIN8e(%{bpJPYa4b{C?T7{C7 z;>!!Dz)hAR4q$2G_~YZcCPfezM2e!> zlztK&|F|B5xT1|0w=1x&`u-R1dv{roOp0kaFGN_Mc=4~Hy$rTgcj>vZTnt!wo%Z4H zk)E)o=?wN_8pdk~qmlsTzXf&See5}^pOia3Ag7FD0%%hXxo*~ycFE(@ z@_UtFGX6@Occ}=5u9zK5pnm0zB81^(v;@lzTId61KWadvbb#qdb87wRpBG$LVLY$B zWVfs5-SFu)Us7QVJ3(qi2WJ-3WaMxC3xP>89|ne#5HUzoFyX)`dZ;ndN*T_X&n2#=|FJ zgiV@4hzBmsji!;=kN@~&vDZ^*vRK2W%@5-qx=hRj4wG=+cwC}i@emw+Ucl|JMj^WV z9G?NCVVU32Pis`Uv2 zGSz+YbT5Pw=n9Cv5&C=6&is|6WaPz<4k@~6C36c&Iyha zUFW0IP?CB0+{bo#fR<8oddC_bRLYBh&9e5f1xl`|lY4hDT7&|N3HD(Zo`tK#D9VNE z_Q9m=YtHN?D=Q1{rBW>6y5v`&;S+kGNuDy(yp1E^mV~uZh>mmzvG-yU)bf(gO!LVe zX5)ryhcd;=a>M5sL>#Z0-X5xF2kFz>Jb%3NL=&vvCnUx(MM%0iFAhWk8|ypoi6@^{ zGs~rmlLK2f#2*# z^6_kHpI(Sl8V*7(ZP&8u;|ypJx*$p8m`af|X7P6>DD{Jo+IDNFzO$IRV#!DsRBA=g zAlJr${)drb1PbnrDWZ44czV+?6B1AF(L{?K2W&V+(|L2K^ryIok%GmSh7#+)itjTV zZOt^fx4MV$oks_u8hQow_RZx{^&>2b2^8baD}?&85G?^!1yMlQos|MZ4AnTS{7GL6 zJKUbyZ9g#4%Oa1D5yWR^%CMdMH_0?ANlu~!ZWZOZ^@FyX(|IAF4g{K(br>>u`+fn1 zQvD>_UGkw3CwcX&4`4v*&0YTGTdaJ3@SaL9ocraQ{75xWG%Tf6{CB3}Rf+`1ax8(A zkgfqr?3X5hID7qZeha5lrxh{-e+;;F8WbLu@_oO3Q9iW7r~(Tn$M`tZGeS*am)7;y zS>ni>|4bLH6yJ=}Vc2TF#cj_tR6%9a%?>HcUj9g%1mjGLNTy6g>>-mu2Us_I2g(XDlY9nYO92OShx;M)8TmAN|u)RHv%-4SH7{ok}r$bBEtw<+k2l@dUTB?8yiOo4&FRkYtmVr8bia9fpL#2~9%nICJwQ5pKw zIH@VDElu`SaZm{B6h8+QRF<*8cclv@XVwQqFwk-a%JlYqJVa?;Ocm0FSnn;J7LYqL z@LhbxTitu>GIqSL{`g^Y-k@K}zNcuFiNN1%p}5 z&<9z(@1Ua^KB@hI)2X`*2CIk0i)@O5u`-bRCXGo)PQBOPtK z>~eqhpo_i>VsfDoO|edCSu+&T$B+&_ZK@nswjB9q-mRWk+w~@;95JApNa_D%8N@eQ zVm3_5(*A4dPn;)K(Nyzh1Y@>WZ7PI-X^S-vJo3Xu z2VU2yQ+8-EE<=ID_R$DI-2~NhF#0N8q@p}D&2P3OX)Y;a(2vPhayaCfgu?FX@gxJc>@tI1l z3t4B} zngXzBsu}f_$*YL`G||YnA((7RqKi?6hgt7F`i=MossE#A7>JR_t7rPUJ60uMoOgFm7Eboy_xh=`kOJwv)l1dD(^1)A?VZGb9&+u3GKfZ^In zwA_mAK&Xcl)uh_d&dr=3E>`e$6yF3p5a+9EO%3%!sbK0LYzW9(!!lxhx&lgEQR^FH z-Wy*wH+5pLUz`6)VcD9EF@{*7so#l(Qv)cT#H6<0i%FJUee`(I377-m)7TfhdmDcx zriU_-h(BV_0p`ig84`n(Kl@9!VIcg2%rL2Mgy+LxJ z9ay;l7T>vhw|t8JH)45RIpa^06pmNo(uvj@E#{q z&^Uj2!y(!j?SSHU>2*W^?3%(i}o;6OA>$oRtkYs^0=vlk{pW!7r6iZme zvc2=7W<%|}(e!I8(%bD>abz$b`HQH&IYY+Ja&q+b^lEW&F@T!NCLe4DN-#1qhBNk+ z_(X!eNNPv-4YDwI%}Z?ytA({x_Y9eQ+wwYuhO&xS&w zM2Yue(slmX*qW@*`+V|uvI(sx89j1IVYLMdKLdu~|C95j+HWB?;o6ekZqkvLYe(}z zF{wr4vpKLJLHg-7okimf4}WQ7=CH+Wj?@X=bK5?i{@(h>2x2;e5DSv}Plu@WGXwVM z@?XweC}mBQ*f4dk{ZC9>?x`@2$+QT&ES&_y^vjDA{R4m!i+PK*W;q8V8KC)cjvb?D zj#J^DDsU*dks#Oo9C@=iH)cj_^JhiU)a)e@)1DOQK*A&9tVWs@W0GU;wWB{8bQgPp zag!Sk9~^1RZpfO&%AuEPWR-$;6ernB;IRonLcv~B9zl>{p zvYuxavl*kscOl8~RI#y^beXh(O-@r4e0mix>p%)KFM_M&HQkWk7wmAzf_Nx5D_Be# zg$lgvSmnYWEp+Bi+ggUa14jft8!P<#i28egEo@<57W7f{?T=O)-w0YwSbG3!S z;JYlIrdOM^woXIJ(j``?Tj`=flVNOqdMe;n@p?ishjnW3=G}l>kL+g#PwH2H{Fje( zxR*(f1NR_s%$QU z0kOghat?Om${)#xo`>WJTfv$oIgp!N7##+xXTP1s zG_B+v^n$n9z>Ai@;2iA5n7);gr8Z2QVu%+*4YD6R_ifZxzOh@e8TsetKdz_Xfu#eu z6wqE6tHKvvk>;Yl0`Ojj2chjEGRm(Ctp%(ly3?<=yMZJ_3Zy4dw6cP8LM)_H^+T-%yhw%x)q=Vyd12iw#$o0jnbE^N zavJO2l1~#8Ef&6;?kV)4J4rpJ1MJM?;|Qmd&i8b9GmZg}qlj%k;lo2Q@MYr6=a5-B z=ob}rjDMH)>ko@(S+G&7|NY&wS0u$Eg0@kg08V3G7Z>!D_=r=zav``kd^E|i1BTjy>#5_65pu2u5*WxBx?Pp7DUzc9&EXjzssgNjR0?|!uo5)wKih|~8rZcp+olxd!VEPCSh@Kt}Q8nP_dWvg~G4Y16+|$qt4V7!%Q)nJEu9si)iq3 zD!qHoBhSyIY#wGsdHNe;?YXm*56waUf)*qw43wJG_$-J&Si&7_NaUD{SBUoBr;ZJG z`+nTx0DLzCbHyp=IO(I?NN_`EG?!Au+e}5&htAd1e?AXD?+*wt; zeoKl0j@FwvXef%jy!Clr-17>d#5FIxCaOlM*qS~EgN>=_^}RLnYAN~$x58}pV$>XK zoW!g|b+ani1h^7vfHe#c#$>8lOSt_6#K0)!H(DPa&yHJokF%y8V zWrf;>PrW`NEK1%$M?4=7qjGlKKBT*V!~`zOX=@F;j$~j02E^{PkM$Mle>9m?<0c@} ziC<0TQ4#jUk9;*kLe5gs!wv9Cz}gTlvWP(}JIih-FrZXB24Y}9s83mFLb-4z5hJ4_ z4k%1v9A|`Uc=BMyXvTFF#!UAHJ(lLiz!%>@DF@jg1c(a5dX%|Ah+J2M6+BR}NZh0; zoJX``DPZ&_hg$S-FS12cL)bv=pnk^P10^Fl%TaV~ztS?W`FbS1b_YS4q~#+|p(|mx zdH=jA7O{ZZFwC0rXNrA(nNvA8){zfRsYV`8>U#!z#Z6eqz^0yZSM}Sq`{55o;U59`r3zt6p`)Z!jF6%ncm!`mtY+>aZp3*FJpx})Qa%p%=b$0WqbzRR zzR#CUMXvs3!x@PsSlgctUlK3^-=QmQ-DC>z&hr&$x@>>Xh69)uVO*27A^5%FqZkP> zikKly*bXI$C90-M&1PbKmGBr$0|m+E*iH}}1(2S%!fiqOX?+^4CFM>Kz zwJWB%JCkQ&dX2SO?h9I9kMBoV!asnIF=M_GdPnv0dogzaig1`ga;4MNuUiKP5251?xEoRsf}c&E?Zk~HIr}$QY))dcRyR2T z0wfc>in0!IArN$qDa2iE?k<-tZgh&R?k{}QLwm-dDNtI=KXJwbuSiYLyQ|z)>YiM!IkaCMTmR*5QPf{6R7w-gqopC z43+iiy5s_ z_3Y=T;Pn3BX)oa_-@tMV$Kbu==vQUR{@q6}N0GAc|E3xWHQ>Jz z0+e2(ngOihY$}yPD?9@gJrzP610c{~1UGCQj}VjIbTV;0=iyATKJ2~ZEWmj~$)VYr zGhpNz4CD6G7g~u2;RmV(aAT@GOh>rPg9uyYE}JcG#kj!*&22c$9s{J;?7DR%_R`}_ zdO;1E4(zC1C?@HNC!XS5;WY^418!m8PlzUxtkp>$gsP$5B+S0scl|C|7UokBD2hMd zVi{!K6q`xz)B$b=9@og)chZnh>I{ia59gcD z16b@Z`1kdH3o|5aui(#!C`Y=Y^K)z+R3(eH2a9{@qN_7j_{pi*j#03b!BioG4F|@p zDJkvn;5Oz}ob^+l>T-h`DD{&kpYQBMs#vU{k!F$c?sPG&j9UlMH+N~Z`F`IuFdZ|~ zOg@cv!R{7$e7DR+*Y*taFypWlz9xVJiKROvB#ms?k3pjVF_{QW2o48OjjWd{#~n3Z zr(9xVa2!4p~zx zh7zFB`Z?oh#6C1e?DIsKToFY-s}VUsLSnvO*j_=k4caFpl3ZLESJ(Fiu${ORj)nm) zA>0uXw$YP8^f)c{0|xMpNe4z2&~1`#pssKJXZ%x>#GHGs;o*kaU}4;Qs*5`DE~c=;9iyy18R&Xbyvlo*>6x z-0NkK;|z3>=z-OT2>;9;8PSzPk#xP63M{QNj1OdLx~$B=Abw%H^K6q|+Wtb-RiO%i zbscT~hj@H1@3CYg0kk46f?~1SW6<<{O>gNe3rZ98xi$oI{5{pTf9A@q$16*7dL`aH z;F!`9t36(g6OKoSc^@>}<5|J|88G|-P7g)+JnW*aTlsmuL%jm+K!|3ulmh?rb^I_6 ze3{#C+5CL!{udYX>%@Y@uWTgwO#Nrrll+nQXR#!?Gyae2LUNzuAMbL&@gGq~(n&a$ zn>TGBA=#|;k7E*>|HqsCH@xJnILKrm416Kj1C4=QOG)%70#*Voc9UTy!)n0WY4der znS=OLAKxK4(F;<4FUY+$JEoB}4KHY%ur;BrzdVTttpPNKiyS(T0%aOLU5G}+6o+y` ztGXwwZB6MJge*gJcGN~4dSp80qL4b4feJidcw&5>mTd&5cyg@n-6dLXUyuO&mh7(x zlRxnR`bV(1F#G1dghPUAYK&9uiJO(Ln8~b=U+5kq4T&La(r!wzs3t^F0_%)h^!kwI zEAu}+9cBithjN|LZdUBGd-O}z|MC;a_4$)!OcM}~957G;aDj*v1<_a$glW+d7#y8^ zs3Sjr_%`~rAu%0=hZU)+a@;o#Kqx(33cr4Ym;i|36M zbrrHE>~g@g=(0z@$jjwp6$we`11fgaVwZ?X;;zNjhc>LJR{KUUs~aun&yI@Jmo2#b zm`}H4Id2pDKtG)EwZ-mCHPZm9vP-619Zj7^QsFdQ*OP82nKqL7E6XYq zT$*xQ+X0gOmoA}~=Xx~PjuN=jZlHHG_%62LcS?CUeb zyu>ft2}@Fb?@J@FiGH-60ff|<^H>q~64^K+E|e5EO>-8}`JA+Wflbxbi?hz8`$(d6 zCxR9WY0ZW^^4D;!jVh~6XqdhlEQHv=0%Fqn>Gh3s6|KDPRo+%q{|?tp^e;p$zMZFj z4LUZntycWhne*qrV&4VZ8h;!Glh^~iBfYIq&<#n~D-*!#*>~S%%b3AXtlReT^yxvTv2Kr$f=%*u#qw1v?0Q;ryb z1oud`fz%?f#Fl=ikUJYmW^>xM^dmLS1;d$mR8T=liOTKaj{PrJBE0p#!$NXwt#yR3 z2Aw~D9)HtMTdG$x`)cY~aY=%_A6@+B+OZSKxY-3H6arZ=4K|S75%evPqn@-OGr}># z!Nyy4lf2*Q^{;o^+>g4_y=LNtKvy=IN4bP!~zy^Yy+YpK4Cl`Au!^y?cd^2f5#mZ&vt ziMbL+8hd0Z z9NIx4Fbc2DxM4H6mjO)Z0Kzv!bn+L)jVE!OZiP13elQwu0#_3=IR(!uTZuZfOeNa5t`qedGdHfxyAp5f+4G@k`=ZK3)ShNg)5 z&M)qZ=vZ3|G0+f(IFPGLcefq~Ixa$iO`brC3D5tyCp$Qi%UEAlTAE};7`h449qVO@ zU}13KRXHXu3xMT9qbhdd|7zZD(lxJ7ZP3hKT07s!|ErSBUuq-vq7jn*YNmlena9l} zpc3;NPC~bC&9SpworkgFp^|%8fTq1c+6p8)U2%X~2efSL3p*m5l{Kst%k7ZE$!F1I zWKd-BXx{E4oC9h4-xwW^be9|rc}jWtROfvt2|BS4P$0YWe(u1^VLob`S3Eo)3UN7W ztC&|hL<@g@F@A7$1mC@iJp(-R=fjgv8AL^T&o$}E#OOqJ59VV=@v3yAvyeD<>zp0v zv$v7jj$lYKV{7(fYMftzNm2o zGzKKUfB?O@_e|GY(#=A$;bJz+uTu|wh)DnXO8R)_p@jDs3Y(aTz2)0Hqyo;H;TU*4 zQd(Ln(T-ax!wVPcpcPo6+A1qJ;FvPtT)YjcXIThDKXmE6#fTL6@_)g zr_r~kzlpE)dG;M+3bgoh#P;A)=bftH9p6w z7kU5G;TaW^+Y%@fscbVe{f?N`GC`PO89AWsLB;TiP|(X%(N4Im^Cq8pa?#7=vpcH+ z;u85+*1|Vh6+GOIG_c5?a6XWrhw_=})x5nQf-u##hk&y5w;Qhm50z;eW7eEnFVHNo zwI5ZYn^d4wkQd2qaXZ*nT0Vnk;CC~e@}CL;V|LlM1DO#_%gn-JkEcHMZaFczQh+u8 z-W2QYzI8<65JHQ`Y9^DXkOlHRvkkHNwFkE==w*7j90)bb8h8gT$gDYqx#ihVldhPa zO(|T}U-xa5r6u}>8{(mF)wUEI2E?}x*sK9p`i{^6eo<>Vn$OB?vt+0@Z#IaWf#nA0 z;OFP3tE&sR(ou0I5BY+*aT?^v&FmJJMR8Ynpt6xLUNavwWM#iWOT?7@Wv=4V{3cR? zxA~~0O`ICd%R`m_og3^yp`3}am(r^@k_xLrOcpY+f6r4qkcAm;X$-tWxqRv z+I{Smto%7Wpwh0-H=KQ~urWg}lJS#ab3Z^Q!>y0&;4VGYA9}ADdjqPfS%T~K-#dKx zFp|tfo?WSU?Y-zK7s}M|bx?W1kUwET;|3{;c(-y(zmH^DF-={z)c|fL;1L3*Q4?#K zqdx`f6EuQcTC5%L#LxmB)nP`+MCg4k5afci4VJG^g^YnzLvh92p~c0bxbmyfQ}-%i zZ?9x{`|$3R+iof)sMB!pKH;y_K&QLyCZhDnI;my+17T^%$Z0HI*`|HSSp?k1W9n=` y=zXk{%IHP^ZGHH^qqqO(HUAd?sJ}eqWr}CDOsYldafhKjVdMMqxr8&5dxL~ diff --git a/docs/images/protocol-packet.tex b/docs/images/protocol-packet.tex new file mode 100644 index 00000000..82449ac0 --- /dev/null +++ b/docs/images/protocol-packet.tex @@ -0,0 +1,24 @@ +\documentclass[border=10pt,png]{standalone} +\usepackage{bytefield} +\usepackage{xcolor} + +\begin{document} + + \definecolor{lightcyan}{rgb}{0.85,1,1} + \definecolor{lightgreen}{rgb}{0.85,1,0.85} + \definecolor{lightred}{rgb}{1,0.85,0.85} + \begin{bytefield}[bitwidth=1.1em]{32} + \bitbox{8}[bgcolor=lightcyan]{SOF} & + \bitbox{8}[bgcolor=lightcyan]{LRC1} & + \bitbox{16}[bgcolor=lightgreen]{CMD} \\ + \bitbox{16}[bgcolor=lightgreen]{STATUS} & + \bitbox{16}[bgcolor=lightgreen]{LEN} \\ + \bitbox{8}[bgcolor=lightgreen]{LRC2} & + \bitbox[tlr]{24}[bgcolor=lightred]{} \\ + \wordbox[lr]{1}[bgcolor=lightred]{DATA} \\ + \wordbox[lr]{1}[bgcolor=lightred]{$\cdots$} \\ + \bitbox[blr]{24}[bgcolor=lightred]{} & + \bitbox{8}[bgcolor=lightred]{LRC3} + \end{bytefield} + +\end{document} diff --git a/docs/protocol.md b/docs/protocol.md index 70f57257..16a55b39 100644 --- a/docs/protocol.md +++ b/docs/protocol.md @@ -2,28 +2,379 @@ **WIP** -## Packets format +## Frame format -The communication with the application is not the easiest but is structured as follows: +The communication between the firmware and the client is made of frames structured as follows: ![](images/protocol-packet.png) -- **SOF**: `1 Byte`, the "Magic Byte" represent the start of a packet, must be `0x11`. -- **LRC1**: `1 Byte`, the LRC ([**L**ongitudinal **R**edundancy **C**heck](https://en.wikipedia.org/wiki/Longitudinal_redundancy_check)) of the `SOF`, must be `0xEF`. -- **CMD**: `2 Bytes` in unsigned [Big Endian](https://en.wikipedia.org/wiki/Endianness) format, each command have been assigned a unique number (e.g. `factoryReset(1020)`), this is what you are sending to the device. -- **STATUS**: `2 Bytes` in unsigned [Big Endian](https://en.wikipedia.org/wiki/Endianness) format. If the direction is from APP to hardware, the status is always `0x0000`. If the direction is from hardware to APP, the status is the result of the command. -- **LEN**: `2 Bytes` in unsigned [Big Endian](https://en.wikipedia.org/wiki/Endianness) format, the length of the data, maximum is `512`. -- **LRC2**: `1 Byte`, the LRC ([**L**ongitudinal **R**edundancy **C**heck](https://en.wikipedia.org/wiki/Longitudinal_redundancy_check)) of the `CMD`, `STATUS` and `LEN`. -- **DATA**: `LEN Bytes`, the data to send or receive, maximum is `512 Bytes`. This could be anything, for example you should sending key type, block number, and the card keys when reading a block. -- **LRC3**: `1 Byte`, the LRC ([**L**ongitudinal **R**edundancy **C**heck](https://en.wikipedia.org/wiki/Longitudinal_redundancy_check)) of the `DATA`. +- **SOF**: `1 byte`, "**S**tart-**O**f-**F**rame byte" represents the start of a packet, and must be equal to `0x11`. +- **LRC1**: `1 byte`, LRC over `SOF` byte, therefore must be equal to `0xEF`. +- **CMD**: `2 bytes`, each command have been assigned a unique number (e.g. `DATA_CMD_SET_SLOT_TAG_NICK` = `1007`). +- **STATUS**: `2 bytes`. + - From client to firmware, the status is always `0x0000`. + - From firmware to client, the status is the result of the command. +- **LEN**: `2 bytes`, length of the `DATA` field, maximum is `512`. +- **LRC2**: `1 byte`, LRC over `CMD|STATUS|LEN` bytes. +- **DATA**: `LEN bytes`, data to be sent or received, maximum is `512 bytes`. This payload depends on the exact command or response to command being used. See [Packet payloads](#packet-payloads) below. +- **LRC3**: `1 byte`, LRC over `DATA` bytes. -The total length of the packet is `LEN + 10` Bytes. For receiving, it is the exact same format. - -Note: LRC2 and LRC3 can be computed equally as covering either the frame from its first byte or from the byte following the previous LRC, because previous LRC nullifies previous bytes LRC computation. +Notes: +* The same frame format is used for commands and for responses. +* All values are **unsigned** values, and if more than one byte, in **network byte order**, aka [Big Endian](https://en.wikipedia.org/wiki/Endianness) byte order. +* The total length of the packet is `LEN + 10` bytes, therefore it is between `10` and `522` bytes. +* The LRC ([**L**ongitudinal **R**edundancy **C**heck](https://en.wikipedia.org/wiki/Longitudinal_redundancy_check)) is the 8-bit two's-complement value of the sum of all bytes modulo $2^8$. +* LRC2 and LRC3 can be computed equally as covering either the frame from its first byte or from the byte following the previous LRC, because previous LRC nullifies previous bytes LRC computation. E.g. LRC3(DATA) == LRC3(whole frame) -## Packet payloads +## Data payloads Each command and response have their own payload formats. -TODO: +Standard response status is `STATUS_DEVICE_SUCCESS` for general commands, `HF_TAG_OK` for HF commands and `LF_TAG_OK` for LF commands. +See [Guidelines](#new-data-payloads-guidelines-for-developers) for more info. + +* **TODO:** remap `tag_specific_type_t` enum. Maybe dissociate LF & HF types in 2 enums +* **TODO:** num_to_bytes bytes_to_num +* **TODO:** mf1_darkside_acquire /nested acquire deep PACKED struct... +* **FIXME:** mf1_get_emulator_config with bits -> bytes (5) with 4 bools <> mf1_get_detection_log with bitfield (2)... + +Beware, slots in protocol count from 0 to 7 (and from 1 to 8 in the CLI...). + +In the following list, "CLI" refers to one typical CLI command using the described protocol command. But it's not a 1:1 match, there can be other protocol commands used by the CLI command and there can be other CLI commands using the same protocol command... + +### 1000: GET_APP_VERSION +* Command: no data +* Response: 2 bytes: `version_major|version_minor` +* CLI: cf `hw version` +### 1001: CHANGE_DEVICE_MODE +* Command: 1 byte. `0x00`=emulator mode, `0x01`=reader mode +* Response: no data +* CLI: cf `hw mode set` +### 1002: GET_DEVICE_MODE +* Command: no data +* Response: data: 1 byte. `0x00`=emulator mode, `0x01`=reader mode +* CLI: cf `hw mode get` +### 1003: SET_ACTIVE_SLOT +* Command: 1 byte. `slot_number` between 0 and 7 +* Response: no data +* CLI: cf `hw slot change` +### 1004: SET_SLOT_TAG_TYPE +* Command: 3 bytes. `slot_number|tag_type[2]` with `slot_number` between 0 and 7 and `tag_type` according to `tag_specific_type_t` enum, U16 in Network byte order. +* Response: no data +* CLI: cf `hw slot type` +### 1005: SET_SLOT_DATA_DEFAULT +* Command: 3 bytes. `slot_number|tag_type[2]` with `slot_number` between 0 and 7 and `tag_type` U16 according to `tag_specific_type_t` enum, U16 in Network byte order. +* Response: no data +* CLI: cf `hw slot init` +### 1006: SET_SLOT_ENABLE +* Command: 2 bytes. `slot_number|enable` with `slot_number` between 0 and 7 and `enable` = `0x01` to enable, `0x00` to disable +* Response: no data +* CLI: cf `hw slot enable` +### 1007: SET_SLOT_TAG_NICK +* Command: 2+N bytes. `slot_number|sense_type|name[N]` with `slot_number` between 0 and 7, `sense_type` according to `tag_sense_type_t` enum and `name` a UTF-8 encoded string of max 32 bytes, no null terminator. +* Response: no data +* CLI: cf `hw slot nick set` +### 1008: GET_SLOT_TAG_NICK +* Command: 2 bytes. `slot_number|sense_type` with `slot_number` between 0 and 7 and `sense_type` according to `tag_sense_type_t` enum. +* Response: a UTF-8 encoded string of max 32 bytes, no null terminator. If no nick name has been recorded in Flash, response status is `STATUS_FLASH_READ_FAIL`. +* CLI: cf `hw slot nick get` +### 1009: SLOT_DATA_CONFIG_SAVE +* Command: no data +* Response: no data +* CLI: cf `hw slot update` +### 1010: ENTER_BOOTLOADER +* Command: no data +* Response: this special command does not return and will interrupt the communication link while rebooting in bootloader mode, needed for DFU. +* CLI: cf `hw dfu` +### 1011: GET_DEVICE_CHIP_ID +* Command: no data +* Response: 8 bytes. nRF `DEVICEID[8]` U64 in Network byte order. +* CLI: cf `hw chipid get` +### 1012: GET_DEVICE_ADDRESS +* Command: no data +* Response: 6 bytes. nRF `DEVICEADDR[6]` U48 in Network byte order. First 2 MSBits forced to `0b11` to match BLE static address. +* CLI: cf `hw address get` +### 1013: SAVE_SETTINGS +* Command: no data +* Response: no data +* CLI: cf `hw settings store` +### 1014: RESET_SETTINGS +* Command: no data +* Response: no data +* CLI: cf `hw settings reset` +### 1015: SET_ANIMATION_MODE +* Command: 1 byte, according to `settings_animation_mode_t` enum. +* Response: no data +* CLI: cf `hw settings animation set` +### 1016: GET_ANIMATION_MODE +* Command: no data +* Response: 1 byte, according to `settings_animation_mode_t` enum. +* CLI: cf `hw settings animation get` +### 1017: GET_GIT_VERSION +* Command: no data +* Response: n bytes, a UTF-8 encoded string, no null terminator. +* CLI: cf `hw version` +### 1018: GET_ACTIVE_SLOT +* Command: no data +* Response: 1 byte +* CLI: cf `hw slot list` +### 1019: GET_SLOT_INFO +* Command: no data +* Response: 32 bytes, 8 tuples `hf_tag_type[2]|lf_tag_type[2]` according to `tag_specific_type_t` enum, for slots from 0 to 7, U16 in Network byte order. +* CLI: cf `hw slot list` +### 1020: WIPE_FDS +* Command: no data +* Response: no data. Status is `STATUS_DEVICE_SUCCESS` or `STATUS_FLASH_WRITE_FAIL`. The device will reboot shortly after this command. +* CLI: cf `hw factory_reset` +### 1023: GET_ENABLED_SLOTS +* Command: no data +* Response: 8 bytes, 8 bool = `0x00` or `0x01`, for slots from 0 to 7 +### 1024: DELETE_SLOT_SENSE_TYPE +* Command: 2 bytes. `slot_number|sense_type` with `slot_number` between 0 and 7 and `sense_type` according to `tag_sense_type_t` enum. +* Response: no data +* CLI: cf `hw factory_reset` +### 1025: GET_BATTERY_INFO +* Command: no data +* Response: 3 bytes, `voltage[2]|percentage`. Voltage: U16 in Network byte order. +* CLI: cf `hw battery` +### 1026: GET_BUTTON_PRESS_CONFIG +* Command: 1 byte. Char `A` or `B` (`a`/`b` tolerated too) +* Response: 1 byte, `button_function` according to `settings_button_function_t` enum. +* CLI: cf `hw settings btnpress get` +### 1027: SET_BUTTON_PRESS_CONFIG +* Command: 2 bytes. `button|button_function` with `button` char `A` or `B` (`a`/`b` tolerated too) and `button_function` according to `settings_button_function_t` enum. +* Response: no data +* CLI: cf `hw settings btnpress set` +### 1028: GET_LONG_BUTTON_PRESS_CONFIG +* Command: 1 byte. Char `A` or `B` (`a`/`b` tolerated too) +* Response: 1 byte, `button_function` according to `settings_button_function_t` enum. +* CLI: cf `hw settings btnpress get` +### 1029: SET_LONG_BUTTON_PRESS_CONFIG +* Command: 2 bytes. `button|button_function` with `button` char `A` or `B` (`a`/`b` tolerated too) and `button_function` according to `settings_button_function_t` enum. +* Response: no data +* CLI: cf `hw settings btnpress set` +### 1030: SET_BLE_PAIRING_KEY +* Command: 6 bytes. 6 ASCII-encoded digits. +* Response: no data +* CLI: cf `hw settings blekey` +### 1031: GET_BLE_PAIRING_KEY +* Command: no data +* Response: 6 bytes. 6 ASCII-encoded digits. +* CLI: cf `hw settings blekey` +### 1032: DELETE_ALL_BLE_BONDS +* Command: no data +* Response: no data +* CLI: cf `hw ble bonds clear` +### 1033: GET_DEVICE_MODEL +* Command: no data +* Response: 1 byte. `hw_version` aka `NRF_DFU_HW_VERSION` (0=Ultra, 1=Lite) +* CLI: cf `hw version` +### 1034: GET_DEVICE_SETTINGS +* Command: no data +* Response: 14 bytes + * `settings_current_version` = `5` + * `animation_mode`, cf [GET_ANIMATION_MODE](#1016-get_animation_mode) + * `btn_press_A`, cf [GET_BUTTON_PRESS_CONFIG](#1026-get_button_press_config) + * `btn_press_B`, cf [GET_BUTTON_PRESS_CONFIG](#1026-get_button_press_config) + * `btn_long_press_A`, cf [GET_LONG_BUTTON_PRESS_CONFIG](#1028-get_long_button_press_config) + * `btn_long_press_B`, cf [GET_LONG_BUTTON_PRESS_CONFIG](#1028-get_long_button_press_config) + * `ble_pairing_enable`, cf [GET_BLE_PAIRING_ENABLE](#1036-get_ble_pairing_enable) + * `ble_pairing_key[6]`, cf [GET_BLE_PAIRING_KEY](#1031-get_ble_pairing_key) +* CLI: unused +### 1035: GET_DEVICE_CAPABILITIES +* Command: no data +* Response: 2*n bytes, a list of supported commands IDs. +* CLI: used internally on connect +### 1036: GET_BLE_PAIRING_ENABLE +* Command: no data +* Response: 1 byte, bool = `0x00` or `0x01` +* CLI: cf `hw settings blepair` +### 1037: SET_BLE_PAIRING_ENABLE +* Command: 1 byte, bool = `0x00` or `0x01` +* Response: no data +* CLI: cf `hw settings blepair` +### 2000: HF14A_SCAN +* Command: no data +* Response: N bytes: `tag1_data|tag2_data|...` with each tag: `uidlen|uid[uidlen]|atqa[2]|sak|atslen|ats[atslen]`. UID, ATQA, SAK and ATS as bytes. +* CLI: cf `hf 14a scan` + +Notes: +* remind that if no tag is present, status will be `HF_TAG_NO` and Response empty. +* at the moment, the firmware supports only one tag, but get your client ready for more! +* `atslen` must not be confused with `ats[0]`==`TL`. So `atslen|ats` = `00` means no ATS while `0100` would be an empty ATS. +### 2001: MF1_DETECT_SUPPORT +* Command: no data +* Response: 1 byte, bool = `0x00` or `0x01` +* CLI: cf `hf 14a info` +### 2002: MF1_DETECT_PRNG +* Command: no data +* Response: 1 byte, according to `mf1_nested_type_t` enum +* CLI: cf `hf 14a info` +### 2003: MF1_DETECT_DARKSIDE +* Command: no data +* Response: 1 byte, according to `mf1_darkside_status_t` enum +* CLI: unused +* **FIXME:** always `CANT_FIX_NT` or watchdog reset on static nonce cards +### 2004: MF1_DARKSIDE_ACQUIRE +* Command: 4 bytes: `type_target|block_target|first_recover|sync_max` +* Response: 1 byte if Darkside failed, according to `mf1_darkside_status_t` enum, + else 25 bytes `darkside_status|uid[4]|nt1[4]|par[4]|ks1[4]|nr[4]|ar[4]` + * `darkside_status` + * `uid[4]` U32 (format expected by `darkside` tool) + * `nt1[4]` U32 + * `par[4]` U32 + * `ks1[4]` U32 + * `nr[4]` U32 + * `ar[4]` U32 +* CLI: cf `hf mf darkside` +* **FIXME:** always `CANT_FIX_NT` or watchdog reset on static nonce cards +### 2005: MF1_DETECT_NT_DIST +* Command: 8 bytes: `type_known|block_known|key_known[6]`. Key as 6 bytes. +* Response: 8 bytes: `uid[4]|dist[4]` + * `uid[4]` U32 (format expected by `nested` tool) + * `dist[4]` U32 +* CLI: cf `hf mf nested` +### 2006: MF1_NESTED_ACQUIRE +* Command: 10 bytes: `type_known|block_known|key_known[6]|type_target|block_target`. Key as 6 bytes. +* Response: N*9 bytes: N tuples of `nt[4]|nt_enc[4]|par` + * `nt[4]` U32 + * `nt_enc[4]` U32 + * `par` +* CLI: cf `hf mf nested` +### 2007: MF1_AUTH_ONE_KEY_BLOCK +* Command: 8 bytes: `type|block|key[6]`. Key as 6 bytes. +* Response: no data +* Status will be `HF_TAG_OK` if auth succeeded, else `MF_ERR_AUTH` +* CLI: cf `hf mf nested` +### 2008: MF1_READ_ONE_BLOCK +* Command: 8 bytes: `type|block|key[6]`. Key as 6 bytes. +* Response: 16 bytes: `block_data[16]` +* CLI: cf `hf mf rdbl` +### 2009: MF1_WRITE_ONE_BLOCK +* Command: 24 bytes: `type|block|key[6]|block_data[16]`. Key as 6 bytes. +* Response: no data +* CLI: cf `hf mf wrbl` +### 3000: EM410X_SCAN +* Command: no data +* Response: 5 bytes. `id[5]`. ID as 5 bytes. +* CLI: cf `lf em read` +### 3001: EM410X_WRITE_TO_T55XX +* Command: 9+N*4 bytes: `id[5]|new_key[4]|old_key1[4]|old_key2[4]|...` (N>=1). . ID as 5 bytes. Keys as 4 bytes. +* Response: no data +* CLI: cf `lf em write` +### 4000: MF1_WRITE_EMU_BLOCK_DATA +* Command: 1+N*16 bytes: `block_start|block_data1[16]|block_data2[16]|...` (1<=N<=31) +* Response: no data +* CLI: cf `hf mf eload` +### 4001: HF14A_SET_ANTI_COLL_DATA +* Command: N bytes: `uidlen|uid[uidlen]|atqa[2]|sak|atslen|ats[atslen]`. UID, ATQA, SAK and ATS as bytes. +* Response: no data +* CLI: cf `hf mf sim` +### 4004: MF1_SET_DETECTION_ENABLE +* Command: 1 byte, bool = `0x00` or `0x01` +* Response: no data +* CLI: cf `hf detection enable` +### 4005: MF1_GET_DETECTION_COUNT +* Command: no data +* Response: 4 bytes, `count[4]`, U32 in Network byte order. +* CLI: cf `hf detection count` +### 4006: MF1_GET_DETECTION_LOG +* Command: 4 bytes, `index`, U32 in Network byte order. +* Response: N*18 bytes. 0<=N<=28 + * `block` + * `...|is_nested|is_key_b` 1-byte bitfield, starting from LSB + * `uid[4]` ? + * `nt[4]` ? + * `nr[4]` ? + * `ar[4]` ? +* CLI: cf `hf detection decrypt` +### 4007: MF1_GET_DETECTION_ENABLE +* Command: no data +* Response: 1 byte, bool = `0x00` or `0x01` +* CLI: cf `hw slot list` +### 4008: MF1_READ_EMU_BLOCK_DATA +* Command: 2 bytes: `block_start|block_count` with 1<=`block_count` <=32 +* Response: `block_count`*16 bytes +* CLI: cf `hf mf eread` +### 4009: MF1_GET_EMULATOR_CONFIG +* Command: no data +* Response: 5 bytes + * `detection`, cf [MF1_GET_DETECTION_ENABLE](#4007-mf1_get_detection_enable) + * `gen1a_mode`, cf [MF1_GET_GEN1A_MODE](#4010-mf1_get_gen1a_mode) + * `gen2_mode`, cf [MF1_GET_GEN2_MODE](#4012-mf1_get_gen2_mode) + * `block_anti_coll_mode`, cf [MF1_GET_BLOCK_ANTI_COLL_MODE](#4014-mf1_get_block_anti_coll_mode) + * `write_mode`, cf [MF1_GET_WRITE_MODE](#4016-mf1_get_write_mode) +* CLI: cf `hw slot list` +### 4010: MF1_GET_GEN1A_MODE +* Command: no data +* Response: 1 byte, bool = `0x00` or `0x01` +* CLI: unused +### 4011: MF1_SET_GEN1A_MODE +* Command: 1 byte, bool = `0x00` or `0x01` +* Response: no data +* CLI: cf `hf mf settings` +### 4012: MF1_GET_GEN2_MODE +* Command: no data +* Response: 1 byte, bool = `0x00` or `0x01` +* CLI: unused +### 4013: MF1_SET_GEN2_MODE +* Command: 1 byte, bool = `0x00` or `0x01` +* Response: no data +* CLI: cf `hf mf settings` +### 4014: MF1_GET_BLOCK_ANTI_COLL_MODE +* Command: no data +* Response: 1 byte, bool = `0x00` or `0x01` +* CLI: unused +### 4015: MF1_SET_BLOCK_ANTI_COLL_MODE +* Command: 1 byte, bool = `0x00` or `0x01` +* Response: no data +* CLI: cf `hf mf settings` +### 4016: MF1_GET_WRITE_MODE +* Command: no data +* Response: 1 byte, according to `nfc_tag_mf1_write_mode_t` aka `MifareClassicWriteMode` enum +* CLI: unused +### 4017: MF1_SET_WRITE_MODE +* Command: 1 byte, according to `nfc_tag_mf1_write_mode_t` aka `MifareClassicWriteMode` enum +* Response: no data +* CLI: cf `hf mf settings` +### 4018: HF14A_GET_ANTI_COLL_DATA +* Command: no data +* Response: no data or N bytes: `uidlen|uid[uidlen]|atqa[2]|sak|atslen|ats[atslen]`. UID, ATQA, SAK and ATS as bytes. +* CLI: cf `hf mf info` +### 5000: EM410X_SET_EMU_ID +* Command: 5 bytes. `id[5]`. ID as 5 bytes. +* Response: no data +* CLI: cf `lf em sim set` +### 5001: EM410X_GET_EMU_ID +* Command: no data +* Response: 5 bytes. `id[5]`. ID as 5 bytes. +* CLI: cf `lf em sim get` + +## New data payloads: guidelines for developers + +If you need to define new payloads for new commands, try to follow these guidelines. + +### Guideline: Verbose and explicit +Be verbose, explicit and reuse conventions, in order to enhance code maintainability and understandability for the other contributors +### Guideline: Structs +- Define C `struct` for cmd/resp data greater than a single byte, use and abuse of `struct.pack`/`struct.unpack` in Python. So one can understand the payload format at a simple glimpse. Exceptions to `C` struct are when the formats are of variable length (but Python `struct` is still flexible enough to cope with such formats!) +- Avoid hardcoding offsets, use `sizeof()`, `offsetof(struct, field)` in C and `struct.calcsize()` in Python +### Guideline: Status +If single byte of data to return, still use a 1-byte `data`, not `status`. Standard response status is `STATUS_DEVICE_SUCCESS` for general commands, `HF_TAG_OK` for HF commands and `LF_TAG_OK` for LF commands. If the response status is different than those, the response data is empty. Response status are generic and cover things like tag disappearance or tag non-conformities with the ISO standard. If a command needs more specific response status, it is added in the first byte of the data, to avoid cluttering the 1-byte general status enum with command-specific statuses. See e.g. [MF1_DARKSIDE_ACQUIRE](#2004-mf1_darkside_acquire). +### Guideline: unambiguous types +- Use unambiguous types such as `uint16_t`, not `int` or `enum`. Cast explicitly `int` and `enum` to `uint_t` of proper size +- Use Network byte order for 16b and 32b integers + - Macros `U16NTOHS`, `U32NTOHL` must be used on reception of a command payload. + - Macros `U16HTONS`, `U32HTONL` must be used on creation of a response payload. + - In Python, use the modifier `!` with all `struct.pack`/`struct.unpack` +### Guideline: payload parsing in handlers +- Concentrate payload parsing in the handlers, avoid further parsing in their callers. Callers should not care about the protocol. This is true for the firmware and the client. +- In cmd_processor handlers: don't reuse input `length`/`data` parameters for creating the response content +### Guideline: Naming conventions +- Use the exact same command and fields names in firmware and in client, use function names matching the command names for their handlers unless there is a very good reason not to do so. This helps grepping around. Names must start with a letter, not a number, because some languages require it (e.g. `14a_scan` not possible in Python) +- Respect commands order in `m_data_cmd_map`, `data_cmd.h` and `chameleon_cmd.py` definitions +- Even if a command is not yet implemented in firmware or in client but a command number is allocated, add it to `data_cmd.h` and `chameleon_cmd.py` with some `FIXME: to be implemented` comment +### Guideline: Validate status and data +- Validate response status in client before parsing data. +- Validate data before using it. diff --git a/firmware/application/src/app_cmd.c b/firmware/application/src/app_cmd.c index 9400edbd..e5b46165 100644 --- a/firmware/application/src/app_cmd.c +++ b/firmware/application/src/app_cmd.c @@ -14,7 +14,7 @@ #include "nrf_pwr_mgmt.h" #include "settings.h" #include "delayed_reset.h" -#include "lwip_def.h" +#include "netdata.h" #define NRF_LOG_MODULE_NAME app_cmd @@ -24,59 +24,66 @@ NRF_LOG_MODULE_REGISTER(); +static void change_slot_auto(uint8_t slot) { + device_mode_t mode = get_device_mode(); + tag_emulation_change_slot(slot, mode != DEVICE_MODE_READER); + light_up_by_slot(); + set_slot_light_color(0); +} + -data_frame_tx_t *cmd_processor_get_version(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - uint16_t version = FW_VER_NUM; - return data_frame_make(cmd, status, 2, (uint8_t *)&version); +static data_frame_tx_t *cmd_processor_get_app_version(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + struct { + uint8_t version_major; + uint8_t version_minor; + } PACKED payload; + payload.version_major = APP_FW_VER_MAJOR; + payload.version_minor = APP_FW_VER_MINOR; + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, sizeof(payload), (uint8_t *)&payload); } -data_frame_tx_t *cmd_processor_get_git_version(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - return data_frame_make(cmd, status, strlen(GIT_VERSION), (uint8_t *)GIT_VERSION); +static data_frame_tx_t *cmd_processor_get_git_version(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, strlen(GIT_VERSION), (uint8_t *)GIT_VERSION); } -data_frame_tx_t *cmd_processor_get_device(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { +static data_frame_tx_t *cmd_processor_get_device_model(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { #if defined(PROJECT_CHAMELEON_ULTRA) - uint8_t device = 1; + uint8_t resp_data = 1; #else - uint8_t device = 0; + uint8_t resp_data = 0; #endif - return data_frame_make(cmd, status, 1, &device); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, sizeof(resp_data), &resp_data); } -data_frame_tx_t *cmd_processor_change_device_mode(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - if (length == 1) { - if (data[0] == 1) { +static data_frame_tx_t *cmd_processor_change_device_mode(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + if ((length != 1) || (data[0] > 1)) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); + } + + if (data[0] == 1) { #if defined(PROJECT_CHAMELEON_ULTRA) - reader_mode_enter(); - return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 0, NULL); + reader_mode_enter(); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 0, NULL); #else - return data_frame_make(cmd, STATUS_NOT_IMPLEMENTED, 0, NULL); + return data_frame_make(cmd, STATUS_NOT_IMPLEMENTED, 0, NULL); #endif - } else { + } else { #if defined(PROJECT_CHAMELEON_ULTRA) - tag_mode_enter(); + tag_mode_enter(); #endif - return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 0, NULL); - } - } else { - return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 0, NULL); } } -data_frame_tx_t *cmd_processor_get_device_mode(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - device_mode_t mode = get_device_mode(); - if (mode == DEVICE_MODE_READER) { - status = 1; - } else { - status = 0; - } - return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 1, (uint8_t *)&status); +static data_frame_tx_t *cmd_processor_get_device_mode(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + uint8_t resp_data = (get_device_mode() == DEVICE_MODE_READER); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, sizeof(resp_data), &resp_data); } -data_frame_tx_t *cmd_processor_enter_bootloader(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { +static data_frame_tx_t *cmd_processor_enter_bootloader(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { // restart to boot #define BOOTLOADER_DFU_GPREGRET_MASK (0xB0) #define BOOTLOADER_DFU_START_BIT_MASK (0x01) @@ -86,39 +93,48 @@ data_frame_tx_t *cmd_processor_enter_bootloader(uint16_t cmd, uint16_t status, u nrf_pwr_mgmt_shutdown(NRF_PWR_MGMT_SHUTDOWN_GOTO_DFU); // Never into here... while (1) __NOP(); + // For the compiler to be happy... + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 0, NULL); } -data_frame_tx_t *cmd_processor_get_device_chip_id(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - uint32_t chip_id[2]; - chip_id[0] = NRF_FICR->DEVICEID[0]; - chip_id[1] = NRF_FICR->DEVICEID[1]; - return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 8, (uint8_t *)(&chip_id[0])); +static data_frame_tx_t *cmd_processor_get_device_chip_id(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + struct { + uint32_t chip_HSW; + uint32_t chip_LSW; + } PACKED payload; + payload.chip_LSW = U32HTONL(NRF_FICR->DEVICEID[0]); + payload.chip_HSW = U32HTONL(NRF_FICR->DEVICEID[1]); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, sizeof(payload), (uint8_t *)&payload); } -data_frame_tx_t *cmd_processor_get_device_address(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - uint32_t device_address[2]; +static data_frame_tx_t *cmd_processor_get_device_address(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { // The FICR value is a just a random number, with no knowledge // of the Bluetooth Specification requirements for random addresses. // So we need to set a Bluetooth LE random address as a static address. // See: https://github.com/zephyrproject-rtos/zephyr/blob/7b6b1328a0cb96fe313a5e2bfc57047471df236e/subsys/bluetooth/controller/hci/nordic/hci_vendor.c#L29 - device_address[0] = NRF_FICR->DEVICEADDR[0]; - device_address[1] = NRF_FICR->DEVICEADDR[1] | 0xC000; - return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 6, (uint8_t *)(&device_address[0])); + + struct { + uint16_t device_address_HSW; + uint32_t device_address_LSW; + } PACKED payload; + payload.device_address_LSW = U32HTONL(NRF_FICR->DEVICEADDR[0]); + payload.device_address_HSW = U16HTONS(NRF_FICR->DEVICEADDR[1] | 0xC000); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, sizeof(payload), (uint8_t *)&payload); } -data_frame_tx_t *cmd_processor_save_settings(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { +static data_frame_tx_t *cmd_processor_save_settings(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { status = settings_save_config(); return data_frame_make(cmd, status, 0, NULL); } -data_frame_tx_t *cmd_processor_reset_settings(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { +static data_frame_tx_t *cmd_processor_reset_settings(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { settings_init_config(); status = settings_save_config(); return data_frame_make(cmd, status, 0, NULL); } -data_frame_tx_t *cmd_processor_get_settings(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - uint8_t settings[7 + BLE_CONNECT_KEY_LEN_MAX] = {}; +static data_frame_tx_t *cmd_processor_get_device_settings(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + uint8_t settings[7 + BLE_PAIRING_KEY_LEN] = {}; settings[0] = SETTINGS_CURRENT_VERSION; // current version settings[1] = settings_get_animation_config(); // animation mode settings[2] = settings_get_button_press_config('A'); // short A button press mode @@ -126,354 +142,411 @@ data_frame_tx_t *cmd_processor_get_settings(uint16_t cmd, uint16_t status, uint1 settings[4] = settings_get_long_button_press_config('A'); // long A button press mode settings[5] = settings_get_long_button_press_config('B'); // long B button press mode settings[6] = settings_get_ble_pairing_enable(); // is device require pairing - memcpy(settings + 7, settings_get_ble_connect_key(), BLE_CONNECT_KEY_LEN_MAX); - return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 7 + BLE_CONNECT_KEY_LEN_MAX, settings); + memcpy(settings + 7, settings_get_ble_connect_key(), BLE_PAIRING_KEY_LEN); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 7 + BLE_PAIRING_KEY_LEN, settings); } -data_frame_tx_t *cmd_processor_set_animation_mode(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - if (length == 1) { - status = STATUS_DEVICE_SUCCESS; - settings_set_animation_config(data[0]); - } else { - status = STATUS_PAR_ERR; +static data_frame_tx_t *cmd_processor_set_animation_mode(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + if ((length != 1) || (data[0] > 2)) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); } - return data_frame_make(cmd, status, 0, NULL); + settings_set_animation_config(data[0]); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 0, NULL); } -data_frame_tx_t *cmd_processor_get_animation_mode(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { +static data_frame_tx_t *cmd_processor_get_animation_mode(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { uint8_t animation_mode = settings_get_animation_config(); - return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 1, (uint8_t *)(&animation_mode)); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 1, &animation_mode); } -data_frame_tx_t *cmd_processor_get_battery_info(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - uint8_t resp[3] = { 0x00 }; - // set voltage - num_to_bytes(batt_lvl_in_milli_volts, 2, resp); - // set percentage - resp[2] = percentage_batt_lvl; - return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, sizeof(resp), resp); +static data_frame_tx_t *cmd_processor_get_battery_info(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + struct { + uint16_t voltage; + uint8_t percent; + } PACKED payload; + payload.voltage = U16HTONS(batt_lvl_in_milli_volts); + payload.percent = percentage_batt_lvl; + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, sizeof(payload), (uint8_t *)&payload); } -data_frame_tx_t *cmd_processor_get_button_press_config(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - uint8_t button_press_config; - if (length == 1 && is_settings_button_type_valid(data[0])) { - button_press_config = settings_get_button_press_config(data[0]); - status = STATUS_DEVICE_SUCCESS; - } else { - length = 0; - status = STATUS_PAR_ERR; +static data_frame_tx_t *cmd_processor_get_button_press_config(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + if ((length != 1) || (!is_settings_button_type_valid(data[0]))) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); } - return data_frame_make(cmd, status, length, (uint8_t *)(&button_press_config)); + uint8_t button_press_config = settings_get_button_press_config(data[0]); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, sizeof(button_press_config), &button_press_config); } -data_frame_tx_t *cmd_processor_set_button_press_config(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - if (length == 2 && is_settings_button_type_valid(data[0])) { - settings_set_button_press_config(data[0], data[1]); - status = STATUS_DEVICE_SUCCESS; - } else { - length = 0; - status = STATUS_PAR_ERR; +static data_frame_tx_t *cmd_processor_set_button_press_config(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + if ((length != 2) || (!is_settings_button_type_valid(data[0]))) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); } - return data_frame_make(cmd, status, 0, NULL); + settings_set_button_press_config(data[0], data[1]); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 0, NULL); } -data_frame_tx_t *cmd_processor_get_long_button_press_config(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - uint8_t button_press_config; - if (length == 1 && is_settings_button_type_valid(data[0])) { - button_press_config = settings_get_long_button_press_config(data[0]); - status = STATUS_DEVICE_SUCCESS; - } else { - length = 0; - status = STATUS_PAR_ERR; +static data_frame_tx_t *cmd_processor_get_long_button_press_config(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + if ((length != 1) || (!is_settings_button_type_valid(data[0]))) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); } - return data_frame_make(cmd, status, length, (uint8_t *)(&button_press_config)); + uint8_t button_press_config = settings_get_long_button_press_config(data[0]); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, sizeof(button_press_config), &button_press_config); } -data_frame_tx_t *cmd_processor_set_long_button_press_config(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - if (length == 2 && is_settings_button_type_valid(data[0])) { - settings_set_long_button_press_config(data[0], data[1]); - status = STATUS_DEVICE_SUCCESS; - } else { - length = 0; - status = STATUS_PAR_ERR; +static data_frame_tx_t *cmd_processor_set_long_button_press_config(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + if ((length != 2) || (!is_settings_button_type_valid(data[0]))) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); } - return data_frame_make(cmd, status, 0, NULL); + settings_set_long_button_press_config(data[0], data[1]); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 0, NULL); } -data_frame_tx_t *cmd_processor_get_ble_pairing_enable(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { +static data_frame_tx_t *cmd_processor_get_ble_pairing_enable(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { uint8_t is_enable = settings_get_ble_pairing_enable(); - return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 1, (uint8_t *)(&is_enable)); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 1, &is_enable); } -data_frame_tx_t *cmd_processor_set_ble_pairing_enable(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - if (length == 1 && (data[0] == true || data[0] == false)) { - settings_set_ble_pairing_enable(data[0]); - status = STATUS_DEVICE_SUCCESS; - } else { - status = STATUS_PAR_ERR; +static data_frame_tx_t *cmd_processor_set_ble_pairing_enable(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + if (length != 1 && data[0] > 1) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); } - return data_frame_make(cmd, status, 0, NULL); + settings_set_ble_pairing_enable(data[0]); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 0, NULL); } #if defined(PROJECT_CHAMELEON_ULTRA) -data_frame_tx_t *cmd_processor_14a_scan(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { +static data_frame_tx_t *cmd_processor_hf14a_scan(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { picc_14a_tag_t taginfo; status = pcd_14a_reader_scan_auto(&taginfo); - if (status == HF_TAG_OK) { - length = sizeof(picc_14a_tag_t); - data = (uint8_t *)&taginfo; - } else { - length = 0; - data = NULL; + if (status != HF_TAG_OK) { + return data_frame_make(cmd, status, 0, NULL); + } + // uidlen[1]|uid[uidlen]|atqa[2]|sak[1]|atslen[1]|ats[atslen] + // dynamic length, so no struct + uint8_t payload[1 + sizeof(taginfo.uid) + sizeof(taginfo.atqa) + sizeof(taginfo.sak) + 1 + 254]; + uint16_t offset = 0; + payload[offset++] = taginfo.uid_len; + memcpy(&payload[offset], taginfo.uid, taginfo.uid_len); + offset += taginfo.uid_len; + memcpy(&payload[offset], taginfo.atqa, sizeof(taginfo.atqa)); + offset += sizeof(taginfo.atqa); + payload[offset++] = taginfo.sak; + payload[offset++] = 0; // TODO: no ATS support yet + return data_frame_make(cmd, HF_TAG_OK, offset, payload); +} + +static data_frame_tx_t *cmd_processor_mf1_detect_support(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + uint8_t support; + status = check_std_mifare_nt_support((bool *)&support); + if (status != HF_TAG_OK) { + return data_frame_make(cmd, status, 0, NULL); } - return data_frame_make(cmd, status, length, data); + return data_frame_make(cmd, HF_TAG_OK, sizeof(support), &support); } -data_frame_tx_t *cmd_processor_detect_mf1_support(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - status = check_std_mifare_nt_support(); - return data_frame_make(cmd, status, 0, NULL); +static data_frame_tx_t *cmd_processor_mf1_detect_prng(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + uint8_t type; + status = check_prng_type((mf1_prng_type_t *)&type); + if (status != HF_TAG_OK) { + return data_frame_make(cmd, status, 0, NULL); + } + return data_frame_make(cmd, HF_TAG_OK, sizeof(type), &type); } -data_frame_tx_t *cmd_processor_detect_mf1_nt_level(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - status = check_weak_nested_support(); - return data_frame_make(cmd, status, 0, NULL); +static data_frame_tx_t *cmd_processor_mf1_detect_darkside(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + uint8_t darkside_status; + status = check_darkside_support((mf1_darkside_status_t *)&darkside_status); + if (status != HF_TAG_OK) { + return data_frame_make(cmd, status, 0, NULL); + } + return data_frame_make(cmd, HF_TAG_OK, sizeof(darkside_status), &darkside_status); } -data_frame_tx_t *cmd_processor_detect_mf1_darkside(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - status = check_darkside_support(); - return data_frame_make(cmd, status, 0, NULL); +static data_frame_tx_t *cmd_processor_mf1_darkside_acquire(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + if (length != 4) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); + } + struct { + uint8_t darkside_status; + // DarksideCore_t is PACKED and comprises only bytes so we can use it directly + DarksideCore_t dc; + } PACKED payload; + status = darkside_recover_key(data[1], data[0], data[2], data[3], &payload.dc, (mf1_darkside_status_t *)&payload.darkside_status); + if (status != HF_TAG_OK) { + return data_frame_make(cmd, status, 0, NULL); + } + if (payload.darkside_status != DARKSIDE_OK) { + return data_frame_make(cmd, HF_TAG_OK, sizeof(payload.darkside_status), &payload.darkside_status); + } + return data_frame_make(cmd, HF_TAG_OK, sizeof(payload), (uint8_t *)&payload); } -data_frame_tx_t *cmd_processor_mf1_darkside_acquire(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - DarksideCore dc; - if (length == 4) { - status = darkside_recover_key(data[1], data[0], data[2], data[3], &dc); - if (status == HF_TAG_OK) { - length = sizeof(DarksideCore); - data = (uint8_t *)(&dc); - } else { - length = 0; - } - } else { - status = STATUS_PAR_ERR; - length = 0; +static data_frame_tx_t *cmd_processor_mf1_detect_nt_dist(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + typedef struct { + uint8_t type_known; + uint8_t block_known; + uint8_t key_known[6]; + } PACKED payload_t; + if (length != sizeof(payload_t)) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); } - return data_frame_make(cmd, status, length, data); -} -data_frame_tx_t *cmd_processor_mf1_nt_distance(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - NestedDist nd; - if (length == 8) { - status = nested_distance_detect(data[1], data[0], &data[2], &nd); - if (status == HF_TAG_OK) { - length = sizeof(NestedDist); - data = (uint8_t *)(&nd); - } else { - length = 0; - } - } else { - status = STATUS_PAR_ERR; - length = 0; + struct { + uint8_t uid[4]; + uint32_t distance; + } PACKED payload_resp; + + payload_t *payload = (payload_t *)data; + uint32_t distance; + status = nested_distance_detect(payload->block_known, payload->type_known, payload->key_known, payload_resp.uid, &distance); + if (status != HF_TAG_OK) { + return data_frame_make(cmd, status, 0, NULL); + } + payload_resp.distance = U32HTONL(distance); + return data_frame_make(cmd, HF_TAG_OK, sizeof(payload_resp), (uint8_t *)&payload_resp); +} + +static data_frame_tx_t *cmd_processor_mf1_nested_acquire(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + NestedCore_t ncs[SETS_NR]; + typedef struct { + uint8_t type_known; + uint8_t block_known; + uint8_t key_known[6]; + uint8_t type_target; + uint8_t block_target; + } PACKED payload_t; + if (length != sizeof(payload_t)) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); } - return data_frame_make(cmd, status, length, data); -} -data_frame_tx_t *cmd_processor_mf1_nested_acquire(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - NestedCore ncs[SETS_NR]; - if (length == 10) { - status = nested_recover_key(bytes_to_num(&data[2], 6), data[1], data[0], data[9], data[8], ncs); - if (status == HF_TAG_OK) { - length = sizeof(ncs); - data = (uint8_t *)(&ncs); - } else { - length = 0; - } - } else { - status = STATUS_PAR_ERR; - length = 0; + payload_t *payload = (payload_t *)data; + status = nested_recover_key(bytes_to_num(payload->key_known, 6), payload->block_known, payload->type_known, payload->block_target, payload->type_target, ncs); + if (status != HF_TAG_OK) { + return data_frame_make(cmd, status, 0, NULL); } - return data_frame_make(cmd, status, length, data); + // NestedCore_t is PACKED and comprises only bytes so we can use it directly + return data_frame_make(cmd, HF_TAG_OK, sizeof(ncs), (uint8_t *)(&ncs)); } -data_frame_tx_t *cmd_processor_mf1_auth_one_key_block(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - if (length == 8) { - status = auth_key_use_522_hw(data[1], data[0], &data[2]); - pcd_14a_reader_mf1_unauth(); - } else { - status = STATUS_PAR_ERR; +static data_frame_tx_t *cmd_processor_mf1_auth_one_key_block(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + typedef struct { + uint8_t type; + uint8_t block; + uint8_t key[6]; + } PACKED payload_t; + if (length != sizeof(payload_t)) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); } + + payload_t *payload = (payload_t *)data; + status = auth_key_use_522_hw(payload->block, payload->type, payload->key); + pcd_14a_reader_mf1_unauth(); return data_frame_make(cmd, status, 0, NULL); } -data_frame_tx_t *cmd_processor_mf1_read_one_block(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { +static data_frame_tx_t *cmd_processor_mf1_read_one_block(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + typedef struct { + uint8_t type; + uint8_t block; + uint8_t key[6]; + } PACKED payload_t; + if (length != sizeof(payload_t)) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); + } + + payload_t *payload = (payload_t *)data; uint8_t block[16] = { 0x00 }; - if (length == 8) { - status = auth_key_use_522_hw(data[1], data[0], &data[2]); - if (status == HF_TAG_OK) { - status = pcd_14a_reader_mf1_read(data[1], block); - if (status == HF_TAG_OK) { - length = 16; - } else { - length = 0; - } - } else { - length = 0; - } - } else { - length = 0; - status = STATUS_PAR_ERR; + status = auth_key_use_522_hw(payload->block, payload->type, payload->key); + if (status != HF_TAG_OK) { + return data_frame_make(cmd, status, 0, NULL); } - return data_frame_make(cmd, status, length, block); + status = pcd_14a_reader_mf1_read(payload->block, block); + if (status != HF_TAG_OK) { + return data_frame_make(cmd, status, 0, NULL); + } + return data_frame_make(cmd, status, sizeof(block), block); } -data_frame_tx_t *cmd_processor_mf1_write_one_block(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - if (length == 24) { - status = auth_key_use_522_hw(data[1], data[0], &data[2]); - if (status == HF_TAG_OK) { - status = pcd_14a_reader_mf1_write(data[1], &data[8]); - } else { - length = 0; - } - } else { - status = STATUS_PAR_ERR; +static data_frame_tx_t *cmd_processor_mf1_write_one_block(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + typedef struct { + uint8_t type; + uint8_t block; + uint8_t key[6]; + uint8_t block_data[16]; + } PACKED payload_t; + if (length != sizeof(payload_t)) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); } + + payload_t *payload = (payload_t *)data; + status = auth_key_use_522_hw(payload->block, payload->type, payload->key); + if (status != HF_TAG_OK) { + return data_frame_make(cmd, status, 0, NULL); + } + status = pcd_14a_reader_mf1_write(payload->block, payload->block_data); return data_frame_make(cmd, status, 0, NULL); } -data_frame_tx_t *cmd_processor_em410x_scan(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { +static data_frame_tx_t *cmd_processor_em410x_scan(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { uint8_t id_buffer[5] = { 0x00 }; status = PcdScanEM410X(id_buffer); - return data_frame_make(cmd, status, sizeof(id_buffer), id_buffer); + if (status != LF_TAG_OK) { + return data_frame_make(cmd, status, 0, NULL); + } + return data_frame_make(cmd, LF_TAG_OK, sizeof(id_buffer), id_buffer); } -data_frame_tx_t *cmd_processor_write_em410x_2_t57(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - if (length >= 13 && (length - 9) % 4 == 0) { - status = PcdWriteT55XX(data, data + 5, data + 9, (length - 9) / 4); - } else { - status = STATUS_PAR_ERR; +static data_frame_tx_t *cmd_processor_em410x_write_to_t55XX(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + typedef struct { + uint8_t id[5]; + uint8_t old_key[4]; + uint8_t new_keys[4]; // we can have more than one... struct just to compute offsets with min 1 key + } PACKED payload_t; + payload_t *payload = (payload_t *)data; + if (length < sizeof(payload_t) || (length - offsetof(payload_t, new_keys)) % sizeof(payload->new_keys) != 0) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); } + + status = PcdWriteT55XX(payload->id, payload->old_key, payload->new_keys, (length - offsetof(payload_t, new_keys)) / sizeof(payload->new_keys)); return data_frame_make(cmd, status, 0, NULL); } #endif -static void change_slot_auto(uint8_t slot) { - device_mode_t mode = get_device_mode(); - tag_emulation_change_slot(slot, mode != DEVICE_MODE_READER); - light_up_by_slot(); - set_slot_light_color(0); +static data_frame_tx_t *cmd_processor_set_active_slot(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + if (length != 1 || data[0] >= TAG_MAX_SLOT_NUM) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); + } + change_slot_auto(data[0]); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 0, NULL); } -data_frame_tx_t *cmd_processor_set_slot_activated(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - if (length == 1 && data[0] < TAG_MAX_SLOT_NUM) { - change_slot_auto(data[0]); - status = STATUS_DEVICE_SUCCESS; - } else { - status = STATUS_PAR_ERR; +static data_frame_tx_t *cmd_processor_set_slot_tag_type(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + typedef struct { + uint8_t num_slot; + uint16_t tag_type; + } PACKED payload_t; + if (length != sizeof(payload_t)) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); } - return data_frame_make(cmd, status, 0, NULL); + + payload_t *payload = (payload_t *)data; + tag_specific_type_t tag_type = U16NTOHS(payload->tag_type); + if (payload->num_slot >= TAG_MAX_SLOT_NUM || tag_type == TAG_TYPE_UNKNOWN) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); + } + tag_emulation_change_type(payload->num_slot, tag_type); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 0, NULL); } -data_frame_tx_t *cmd_processor_set_slot_tag_type(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - if (length == 2 && data[0] < TAG_MAX_SLOT_NUM && data[1] != TAG_TYPE_UNKNOWN) { - uint8_t num_slot = data[0]; - uint8_t tag_type = data[1]; - tag_emulation_change_type(num_slot, (tag_specific_type_t)tag_type); - status = STATUS_DEVICE_SUCCESS; - } else { - status = STATUS_PAR_ERR; +static data_frame_tx_t *cmd_processor_delete_slot_sense_type(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + typedef struct { + uint8_t num_slot; + uint8_t sense_type; + } PACKED payload_t; + payload_t *payload = (payload_t *)data; + if ((length != sizeof(payload_t)) || + (payload->num_slot >= TAG_MAX_SLOT_NUM) || + (payload->sense_type != TAG_SENSE_HF && payload->sense_type != TAG_SENSE_LF)) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); } - return data_frame_make(cmd, status, 0, NULL); + + tag_emulation_delete_data(payload->num_slot, payload->sense_type); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 0, NULL); } -data_frame_tx_t *cmd_processor_delete_slot_sense_type(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - status = STATUS_PAR_ERR; - if (length == 2 && data[0] < TAG_MAX_SLOT_NUM && (data[1] == TAG_SENSE_HF || data[1] == TAG_SENSE_LF)) { - uint8_t slot_num = data[0]; - uint8_t sense_type = data[1]; +static data_frame_tx_t *cmd_processor_set_slot_data_default(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + typedef struct { + uint8_t num_slot; + uint16_t tag_type; + } PACKED payload_t; + if (length != sizeof(payload_t)) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); + } - tag_emulation_delete_data(slot_num, sense_type); - status = STATUS_DEVICE_SUCCESS; + payload_t *payload = (payload_t *)data; + tag_specific_type_t tag_type = U16NTOHS(payload->tag_type); + if (payload->num_slot >= TAG_MAX_SLOT_NUM || tag_type == TAG_TYPE_UNKNOWN) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); } + status = tag_emulation_factory_data(payload->num_slot, tag_type) ? STATUS_DEVICE_SUCCESS : STATUS_NOT_IMPLEMENTED; return data_frame_make(cmd, status, 0, NULL); } -data_frame_tx_t *cmd_processor_set_slot_data_default(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - if (length == 2 && data[0] < TAG_MAX_SLOT_NUM && data[1] != TAG_TYPE_UNKNOWN) { - uint8_t num_slot = data[0]; - uint8_t tag_type = data[1]; - status = tag_emulation_factory_data(num_slot, (tag_specific_type_t)tag_type) ? STATUS_DEVICE_SUCCESS : STATUS_NOT_IMPLEMENTED; - } else { - status = STATUS_PAR_ERR; +static data_frame_tx_t *cmd_processor_set_slot_enable(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + typedef struct { + uint8_t slot_index; + uint8_t enabled; + } PACKED payload_t; + + payload_t *payload = (payload_t *)data; + if (length != sizeof(payload_t) || + payload->slot_index >= TAG_MAX_SLOT_NUM || + payload->enabled > 1) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); } - return data_frame_make(cmd, status, 0, NULL); -} -data_frame_tx_t *cmd_processor_set_slot_enable(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - if (length == 2 && data[0] < TAG_MAX_SLOT_NUM && (data[1] == 0 || data[1] == 1)) { - uint8_t slot_now = data[0]; - bool enable = data[1]; - tag_emulation_slot_set_enable(slot_now, enable); - if (!enable) { - uint8_t slot_prev = tag_emulation_slot_find_next(slot_now); - NRF_LOG_INFO("slot_now = %d, slot_prev = %d", slot_now, slot_prev); - if (slot_prev == slot_now) { - set_slot_light_color(3); - } else { - change_slot_auto(slot_prev); - } + uint8_t slot_now = payload->slot_index; + bool enable = payload->enabled; + tag_emulation_slot_set_enable(slot_now, enable); + if (!enable) { + uint8_t slot_prev = tag_emulation_slot_find_next(slot_now); + NRF_LOG_INFO("slot_now = %d, slot_prev = %d", slot_now, slot_prev); + if (slot_prev == slot_now) { + set_slot_light_color(3); + } else { + change_slot_auto(slot_prev); } - status = STATUS_DEVICE_SUCCESS; - } else { - status = STATUS_PAR_ERR; } - return data_frame_make(cmd, status, 0, NULL); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 0, NULL); } -data_frame_tx_t *cmd_processor_slot_data_config_save(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { +static data_frame_tx_t *cmd_processor_slot_data_config_save(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { tag_emulation_save(); return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 0, NULL); } -data_frame_tx_t *cmd_processor_get_activated_slot(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { +static data_frame_tx_t *cmd_processor_get_active_slot(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { uint8_t slot = tag_emulation_get_slot(); return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 1, &slot); } -data_frame_tx_t *cmd_processor_get_slot_info(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - uint8_t slot_info[16] = {}; +static data_frame_tx_t *cmd_processor_get_slot_info(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + struct { + uint16_t hf_tag_type; + uint16_t lf_tag_type; + } PACKED payload[8]; + tag_specific_type_t tag_type[2]; for (uint8_t slot = 0; slot < 8; slot++) { tag_emulation_get_specific_type_by_slot(slot, tag_type); - slot_info[slot * 2] = tag_type[0]; - slot_info[slot * 2 + 1] = tag_type[1]; + payload[slot].hf_tag_type = U16HTONS(tag_type[0]); + payload[slot].lf_tag_type = U16HTONS(tag_type[1]); } - return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 16, slot_info); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, sizeof(payload), (uint8_t *)&payload); } -data_frame_tx_t *cmd_processor_wipe_fds(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { +static data_frame_tx_t *cmd_processor_wipe_fds(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { bool success = fds_wipe(); status = success ? STATUS_DEVICE_SUCCESS : STATUS_FLASH_WRITE_FAIL; delayed_reset(50); return data_frame_make(cmd, status, 0, NULL); } -data_frame_tx_t *cmd_processor_set_em410x_emu_id(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - if (length == LF_EM410X_TAG_ID_SIZE) { - tag_data_buffer_t *buffer = get_buffer_by_tag_type(TAG_TYPE_EM410X); - memcpy(buffer->buffer, data, LF_EM410X_TAG_ID_SIZE); - tag_emulation_load_by_buffer(TAG_TYPE_EM410X, false); - status = STATUS_DEVICE_SUCCESS; - } else { - status = STATUS_PAR_ERR; +static data_frame_tx_t *cmd_processor_em410x_set_emu_id(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + if (length != LF_EM410X_TAG_ID_SIZE) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); } - return data_frame_make(cmd, status, 0, NULL); + tag_data_buffer_t *buffer = get_buffer_by_tag_type(TAG_TYPE_EM410X); + memcpy(buffer->buffer, data, LF_EM410X_TAG_ID_SIZE); + tag_emulation_load_by_buffer(TAG_TYPE_EM410X, false); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 0, NULL); } -data_frame_tx_t *cmd_processor_get_em410x_emu_id(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { +static data_frame_tx_t *cmd_processor_em410x_get_emu_id(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { tag_specific_type_t tag_type[2]; tag_emulation_get_specific_type_by_slot(tag_emulation_get_slot(), tag_type); if (tag_type[1] == TAG_TYPE_UNKNOWN) { @@ -485,348 +558,272 @@ data_frame_tx_t *cmd_processor_get_em410x_emu_id(uint16_t cmd, uint16_t status, return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, LF_EM410X_TAG_ID_SIZE, responseData); } -data_frame_tx_t *cmd_processor_get_mf1_anti_coll_data(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { +static data_frame_tx_t *cmd_processor_hf14a_get_anti_coll_data(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { tag_specific_type_t tag_type[2]; tag_emulation_get_specific_type_by_slot(tag_emulation_get_slot(), tag_type); if (tag_type[0] == TAG_TYPE_UNKNOWN) { - return data_frame_make(cmd, STATUS_PAR_ERR, 0, data); // no data in slot, don't send garbage + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 0, NULL); // no data in slot, don't send garbage } - uint8_t responseData[16] = {}; nfc_tag_14a_coll_res_reference_t *info = get_saved_mifare_coll_res(); - memcpy(responseData, info->uid, *info->size); - responseData[10] = *info->size; // size is 2 byte len, but... - responseData[12] = info->sak[0]; - memcpy(&responseData[13], info->atqa, 2); - return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 16, responseData); -} - -data_frame_tx_t *cmd_processor_set_mf1_detection_enable(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - if (length == 1 && (data[0] == 0 || data[0] == 1)) { - nfc_tag_mf1_detection_log_clear(); - nfc_tag_mf1_set_detection_enable(data[0]); - status = STATUS_DEVICE_SUCCESS; + // uidlen[1]|uid[uidlen]|atqa[2]|sak[1]|atslen[1]|ats[atslen] + // dynamic length, so no struct + uint8_t payload[1 + *info->size + 2 + 1 + 1 + 254]; + uint16_t offset = 0; + payload[offset++] = *info->size; + memcpy(&payload[offset], info->uid, *info->size); + offset += *info->size; + memcpy(&payload[offset], info->atqa, 2); + offset += 2; + payload[offset++] = *info->sak; + if (info->ats->length > 0) { + payload[offset++] = info->ats->length; + memcpy(&payload[offset], info->ats->data, info->ats->length); + offset += info->ats->length; } else { - status = STATUS_PAR_ERR; + payload[offset++] = 0; } - return data_frame_make(cmd, status, 0, NULL); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, offset, payload); } -data_frame_tx_t *cmd_processor_get_mf1_detection_status(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - if (nfc_tag_mf1_is_detection_enable()) { - status = 1; - } else { - status = 0; +static data_frame_tx_t *cmd_processor_mf1_set_detection_enable(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + if (length != 1 || data[0] > 1) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); } - return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 1, (uint8_t *)&status); + nfc_tag_mf1_detection_log_clear(); + nfc_tag_mf1_set_detection_enable(data[0]); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 0, NULL); +} + +static data_frame_tx_t *cmd_processor_mf1_get_detection_enable(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + uint8_t is_enable = nfc_tag_mf1_is_detection_enable(); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 1, (uint8_t *)(&is_enable)); } -data_frame_tx_t *cmd_processor_get_mf1_detection_count(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { +static data_frame_tx_t *cmd_processor_mf1_get_detection_count(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { uint32_t count = nfc_tag_mf1_detection_log_count(); if (count == 0xFFFFFFFF) { count = 0; } - status = STATUS_DEVICE_SUCCESS; - return data_frame_make(cmd, status, sizeof(uint32_t), (uint8_t *)&count); + uint32_t payload = U32HTONL(count); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, sizeof(uint32_t), (uint8_t *)&payload); } -data_frame_tx_t *cmd_processor_get_mf1_detection_log(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { +static data_frame_tx_t *cmd_processor_mf1_get_detection_log(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { uint32_t count; uint32_t index; uint8_t *resp = NULL; - nfc_tag_mf1_auth_log_t *logs = get_mf1_auth_log(&count); - if (length == 4) { - if (count == 0xFFFFFFFF) { - length = 0; - status = STATUS_PAR_ERR; - } else { - index = bytes_to_num(data, 4); - // NRF_LOG_INFO("index = %d", index); - if (index < count) { - resp = (uint8_t *)(logs + index); - length = MIN(count - index, DATA_PACK_MAX_DATA_LENGTH / sizeof(nfc_tag_mf1_auth_log_t)); - length = length * sizeof(nfc_tag_mf1_auth_log_t); - status = STATUS_DEVICE_SUCCESS; - } else { - length = 0; - status = STATUS_PAR_ERR; - } - } - } else { - length = 0; - status = STATUS_PAR_ERR; + nfc_tag_mf1_auth_log_t *logs = mf1_get_auth_log(&count); + if (length != 4 || count == 0xFFFFFFFF) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); + } + index = U32NTOHL(*(uint32_t *)data); + // NRF_LOG_INFO("index = %d", index); + if (index >= count) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); } - return data_frame_make(cmd, status, length, resp); + resp = (uint8_t *)(logs + index); + length = MIN(count - index, NETDATA_MAX_DATA_LENGTH / sizeof(nfc_tag_mf1_auth_log_t)) * sizeof(nfc_tag_mf1_auth_log_t); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, length, resp); } -data_frame_tx_t *cmd_processor_set_mf1_emulator_block(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - if (length > 0 && (((length - 1) % NFC_TAG_MF1_DATA_SIZE) == 0)) { - uint8_t block_index = data[0]; - uint8_t block_count = (length - 1) / NFC_TAG_MF1_DATA_SIZE; - if (block_index + block_count > NFC_TAG_MF1_BLOCK_MAX) { - status = STATUS_PAR_ERR; - } else { - tag_data_buffer_t *buffer = get_buffer_by_tag_type(TAG_TYPE_MIFARE_4096); - nfc_tag_mf1_information_t *info = (nfc_tag_mf1_information_t *)buffer->buffer; - for (int i = 1, j = block_index; i < length; i += NFC_TAG_MF1_DATA_SIZE, j++) { - uint8_t *p_block = &data[i]; - memcpy(info->memory[j], p_block, NFC_TAG_MF1_DATA_SIZE); - } - status = STATUS_DEVICE_SUCCESS; - } - } else { +static data_frame_tx_t *cmd_processor_mf1_write_emu_block_data(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + if (length == 0 || (((length - 1) % NFC_TAG_MF1_DATA_SIZE) != 0)) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); + } + uint8_t block_index = data[0]; + uint8_t block_count = (length - 1) / NFC_TAG_MF1_DATA_SIZE; + if (block_index + block_count > NFC_TAG_MF1_BLOCK_MAX) { status = STATUS_PAR_ERR; } - return data_frame_make(cmd, status, 0, NULL); + tag_data_buffer_t *buffer = get_buffer_by_tag_type(TAG_TYPE_MIFARE_4096); + nfc_tag_mf1_information_t *info = (nfc_tag_mf1_information_t *)buffer->buffer; + for (int i = 1, j = block_index; i < length; i += NFC_TAG_MF1_DATA_SIZE, j++) { + uint8_t *p_block = &data[i]; + memcpy(info->memory[j], p_block, NFC_TAG_MF1_DATA_SIZE); + } + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 0, NULL); } -data_frame_tx_t *cmd_processor_get_mf1_emulator_block(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - if (length == 2) { - uint8_t block_index = data[0]; - uint8_t block_count = data[1]; - - // block_count > 32 will overflow the maximum message size - if (block_count != 0 && block_count <= 32 && (uint16_t) block_index + block_count < NFC_TAG_MF1_BLOCK_MAX) { - tag_data_buffer_t *buffer = get_buffer_by_tag_type(TAG_TYPE_MIFARE_4096); - nfc_tag_mf1_information_t *info = (nfc_tag_mf1_information_t *)buffer->buffer; - uint16_t result_length = block_count * NFC_TAG_MF1_DATA_SIZE; - uint8_t result_buffer[result_length]; - for (int i = 0, j = block_index; i < result_length; i += NFC_TAG_MF1_DATA_SIZE, j++) { - uint8_t *p_block = &result_buffer[i]; - memcpy(p_block, info->memory[j], NFC_TAG_MF1_DATA_SIZE); - } - - return data_frame_make(cmd, status, result_length, result_buffer); - } +static data_frame_tx_t *cmd_processor_mf1_read_emu_block_data(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + if ((length != 2) || (data[1] < 1) || (data[1] > 32) || (data[0] + data[1] > NFC_TAG_MF1_BLOCK_MAX)) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); } - - return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); + uint8_t block_index = data[0]; + uint8_t block_count = data[1]; + tag_data_buffer_t *buffer = get_buffer_by_tag_type(TAG_TYPE_MIFARE_4096); + nfc_tag_mf1_information_t *info = (nfc_tag_mf1_information_t *)buffer->buffer; + uint16_t result_length = block_count * NFC_TAG_MF1_DATA_SIZE; + uint8_t result_buffer[result_length]; + for (int i = 0, j = block_index; i < result_length; i += NFC_TAG_MF1_DATA_SIZE, j++) { + memcpy(&result_buffer[i], info->memory[j], NFC_TAG_MF1_DATA_SIZE); + } + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, result_length, result_buffer); } -data_frame_tx_t *cmd_processor_set_mf1_anti_collision_res(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - if (length > 13) { - // sak(1) + atqa(2) + uid(10) - status = STATUS_PAR_ERR; - } else { - uint8_t uid_length = length - 3; - if (is_valid_uid_size(uid_length)) { - nfc_tag_14a_coll_res_reference_t *info = get_mifare_coll_res(); - // copy sak - info->sak[0] = data[0]; - // copy atqa - memcpy(info->atqa, &data[1], 2); - // copy uid - memcpy(info->uid, &data[3], uid_length); - // copy size - *(info->size) = (nfc_tag_14a_uid_size)uid_length; - status = STATUS_DEVICE_SUCCESS; - } else { - status = STATUS_PAR_ERR; - } +static data_frame_tx_t *cmd_processor_hf14a_set_anti_coll_data(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + // uidlen[1]|uid[uidlen]|atqa[2]|sak[1]|atslen[1]|ats[atslen] + // dynamic length, so no struct + if ((length < 1) || \ + (!is_valid_uid_size(data[0])) || \ + (length < 1 + data[0] + 2 + 1 + 1) || \ + (length < 1 + data[0] + 2 + 1 + 1 + data[1 + data[0] + 2 + 1])) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); } - return data_frame_make(cmd, status, 0, NULL); + nfc_tag_14a_coll_res_reference_t *info = get_mifare_coll_res(); + uint16_t offset = 0; + *(info->size) = (nfc_tag_14a_uid_size)data[offset]; + offset++; + memcpy(info->uid, &data[offset], *(info->size)); + offset += *(info->size); + memcpy(info->atqa, &data[offset], 2); + offset += 2; + info->sak[0] = data[offset]; + offset ++; + info->ats->length = data[offset]; + offset ++; + memcpy(info->ats->data, &data[offset], info->ats->length); + offset += info->ats->length; + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 0, NULL); } -data_frame_tx_t *cmd_processor_set_slot_tag_nick_name(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - if (length > 34 || length < 3) { - status = STATUS_PAR_ERR; - } else { - uint8_t slot = data[0]; - uint8_t sense_type = data[1]; - fds_slot_record_map_t map_info; - - get_fds_map_by_slot_sense_type_for_nick(slot, sense_type, &map_info); +static data_frame_tx_t *cmd_processor_set_slot_tag_nick(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + if (length < 3 || length > 34) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); + } + uint8_t slot = data[0]; + uint8_t sense_type = data[1]; + fds_slot_record_map_t map_info; + if (slot >= TAG_MAX_SLOT_NUM || (sense_type != TAG_SENSE_HF && sense_type != TAG_SENSE_LF)) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); + } + get_fds_map_by_slot_sense_type_for_nick(slot, sense_type, &map_info); - uint8_t buffer[36]; - buffer[0] = length - 2; - memcpy(buffer + 1, data + 2, buffer[0]); + uint8_t buffer[36]; + buffer[0] = length - 2; + memcpy(buffer + 1, data + 2, buffer[0]); - bool ret = fds_write_sync(map_info.id, map_info.key, sizeof(buffer) / 4, buffer); - if (ret) { - status = STATUS_DEVICE_SUCCESS; - } else { - status = STATUS_FLASH_WRITE_FAIL; - } + bool ret = fds_write_sync(map_info.id, map_info.key, sizeof(buffer) / 4, buffer); + if (!ret) { + return data_frame_make(cmd, STATUS_FLASH_WRITE_FAIL, 0, NULL); } - return data_frame_make(cmd, status, 0, NULL); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 0, NULL); } -data_frame_tx_t *cmd_processor_get_slot_tag_nick_name(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { +static data_frame_tx_t *cmd_processor_get_slot_tag_nick(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { if (length != 2) { - status = STATUS_PAR_ERR; - return data_frame_make(cmd, status, 0, NULL); - } else { - uint8_t buffer[36]; - uint8_t slot = data[0]; - uint8_t sense_type = data[1]; - fds_slot_record_map_t map_info; - - get_fds_map_by_slot_sense_type_for_nick(slot, sense_type, &map_info); - bool ret = fds_read_sync(map_info.id, map_info.key, sizeof(buffer), buffer); - if (ret) { - status = STATUS_DEVICE_SUCCESS; - length = buffer[0]; - data = &buffer[1]; - } else { - status = STATUS_FLASH_READ_FAIL; - length = 0; - data = NULL; - } - // must be called within stack allocation of buffer - return data_frame_make(cmd, status, length, data); + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); } + uint8_t slot = data[0]; + uint8_t sense_type = data[1]; + uint8_t buffer[36]; + fds_slot_record_map_t map_info; + + if (slot >= TAG_MAX_SLOT_NUM || (sense_type != TAG_SENSE_HF && sense_type != TAG_SENSE_LF)) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); + } + get_fds_map_by_slot_sense_type_for_nick(slot, sense_type, &map_info); + bool ret = fds_read_sync(map_info.id, map_info.key, sizeof(buffer), buffer); + if (!ret) { + return data_frame_make(cmd, STATUS_FLASH_READ_FAIL, 0, NULL); + } + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, buffer[0], &buffer[1]); } -data_frame_tx_t *cmd_processor_get_mf1_info(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { +static data_frame_tx_t *cmd_processor_mf1_get_emulator_config(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { uint8_t mf1_info[5] = {}; mf1_info[0] = nfc_tag_mf1_is_detection_enable(); mf1_info[1] = nfc_tag_mf1_is_gen1a_magic_mode(); mf1_info[2] = nfc_tag_mf1_is_gen2_magic_mode(); mf1_info[3] = nfc_tag_mf1_is_use_mf1_coll_res(); - nfc_tag_mf1_write_mode_t write_mode = nfc_tag_mf1_get_write_mode(); - if (write_mode == NFC_TAG_MF1_WRITE_NORMAL) { - mf1_info[4] = 0; - } else if (write_mode == NFC_TAG_MF1_WRITE_DENIED) { - mf1_info[4] = 1; - } else if (write_mode == NFC_TAG_MF1_WRITE_DECEIVE) { - mf1_info[4] = 2; - } else if (write_mode == NFC_TAG_MF1_WRITE_SHADOW) { - mf1_info[4] = 3; - } + mf1_info[4] = nfc_tag_mf1_get_write_mode(); return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 5, mf1_info); } -data_frame_tx_t *cmd_processor_get_mf1_gen1a_magic_mode(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - if (nfc_tag_mf1_is_gen1a_magic_mode()) { - status = 1; - } else { - status = 0; - } - return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 1, (uint8_t *)&status); +static data_frame_tx_t *cmd_processor_mf1_get_gen1a_mode(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + uint8_t mode = nfc_tag_mf1_is_gen1a_magic_mode(); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 1, &mode); } -data_frame_tx_t *cmd_processor_set_mf1_gen1a_magic_mode(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - if (length == 1 && (data[0] == 0 || data[0] == 1)) { - nfc_tag_mf1_set_gen1a_magic_mode(data[0]); - status = STATUS_DEVICE_SUCCESS; - } else { - status = STATUS_PAR_ERR; +static data_frame_tx_t *cmd_processor_mf1_set_gen1a_mode(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + if (length != 1 && data[0] > 1) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); } - return data_frame_make(cmd, status, 0, NULL); + nfc_tag_mf1_set_gen1a_magic_mode(data[0]); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 0, NULL); } -data_frame_tx_t *cmd_processor_get_mf1_gen2_magic_mode(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - if (nfc_tag_mf1_is_gen2_magic_mode()) { - status = 1; - } else { - status = 0; - } - return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 1, (uint8_t *)&status); +static data_frame_tx_t *cmd_processor_mf1_get_gen2_mode(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + uint8_t mode = nfc_tag_mf1_is_gen2_magic_mode(); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 1, &mode); } -data_frame_tx_t *cmd_processor_set_mf1_gen2_magic_mode(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - if (length == 1 && (data[0] == 0 || data[0] == 1)) { - nfc_tag_mf1_set_gen2_magic_mode(data[0]); - status = STATUS_DEVICE_SUCCESS; - } else { - status = STATUS_PAR_ERR; +static data_frame_tx_t *cmd_processor_mf1_set_gen2_mode(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + if (length != 1 && data[0] > 1) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); } - return data_frame_make(cmd, status, 0, NULL); + nfc_tag_mf1_set_gen2_magic_mode(data[0]); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 0, NULL); } -data_frame_tx_t *cmd_processor_get_mf1_use_coll_res(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - if (nfc_tag_mf1_is_use_mf1_coll_res()) { - status = 1; - } else { - status = 0; - } - return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 1, (uint8_t *)&status); +static data_frame_tx_t *cmd_processor_mf1_get_block_anti_coll_mode(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + uint8_t mode = nfc_tag_mf1_is_use_mf1_coll_res(); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 1, &mode); } -data_frame_tx_t *cmd_processor_set_mf1_use_coll_res(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - if (length == 1 && (data[0] == 0 || data[0] == 1)) { - nfc_tag_mf1_set_use_mf1_coll_res(data[0]); - status = STATUS_DEVICE_SUCCESS; - } else { - status = STATUS_PAR_ERR; +static data_frame_tx_t *cmd_processor_mf1_set_block_anti_coll_mode(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + if (length != 1 && data[0] > 1) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); } - return data_frame_make(cmd, status, 0, NULL); + nfc_tag_mf1_set_use_mf1_coll_res(data[0]); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 0, NULL); } -data_frame_tx_t *cmd_processor_get_mf1_write_mode(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - nfc_tag_mf1_write_mode_t write_mode = nfc_tag_mf1_get_write_mode(); - if (write_mode == NFC_TAG_MF1_WRITE_NORMAL) { - status = 0; - } else if (write_mode == NFC_TAG_MF1_WRITE_DENIED) { - status = 1; - } else if (write_mode == NFC_TAG_MF1_WRITE_DECEIVE) { - status = 2; - } else if (write_mode == NFC_TAG_MF1_WRITE_SHADOW) { - status = 3; - } - return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 1, (uint8_t *)&status); -} - -data_frame_tx_t *cmd_processor_set_mf1_write_mode(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - if (length == 1 && (data[0] >= 0 || data[0] <= 3)) { - uint8_t mode = data[0]; - if (mode == 0) { - nfc_tag_mf1_set_write_mode(NFC_TAG_MF1_WRITE_NORMAL); - } else if (mode == 1) { - nfc_tag_mf1_set_write_mode(NFC_TAG_MF1_WRITE_DENIED); - } else if (mode == 2) { - nfc_tag_mf1_set_write_mode(NFC_TAG_MF1_WRITE_DECEIVE); - } else if (mode == 3) { - nfc_tag_mf1_set_write_mode(NFC_TAG_MF1_WRITE_SHADOW); - } - status = STATUS_DEVICE_SUCCESS; - } else { - status = STATUS_PAR_ERR; +static data_frame_tx_t *cmd_processor_mf1_get_write_mode(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + uint8_t mode = nfc_tag_mf1_get_write_mode(); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 1, &mode); +} + +static data_frame_tx_t *cmd_processor_mf1_set_write_mode(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + if (length != 1 || data[0] > 3) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); } - return data_frame_make(cmd, status, 0, NULL); + nfc_tag_mf1_set_write_mode(data[0]); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 0, NULL); } -data_frame_tx_t *cmd_processor_get_enabled_slots(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { +static data_frame_tx_t *cmd_processor_get_enabled_slots(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { uint8_t slot_info[8] = {}; for (uint8_t slot = 0; slot < 8; slot++) { slot_info[slot] = tag_emulation_slot_is_enable(slot); } - - return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 8, slot_info); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, sizeof(slot_info), slot_info); } -data_frame_tx_t *cmd_processor_get_ble_connect_key(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - return data_frame_make( - cmd, - STATUS_DEVICE_SUCCESS, - BLE_CONNECT_KEY_LEN_MAX, // 6 - settings_get_ble_connect_key() // Get key point from config - ); +static data_frame_tx_t *cmd_processor_get_ble_connect_key(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, BLE_PAIRING_KEY_LEN, settings_get_ble_connect_key()); } -data_frame_tx_t *cmd_processor_set_ble_connect_key(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - if (length == BLE_CONNECT_KEY_LEN_MAX) { - // Must be 6 ASCII characters, can only be 0-9. - bool is_valid_key = true; - for (uint8_t i = 0; i < BLE_CONNECT_KEY_LEN_MAX; i++) { - if (data[i] < 48 || data[i] > 57) { - is_valid_key = false; - break; - } - } - if (is_valid_key) { - // Key is valid, we can update to config - settings_set_ble_connect_key(data); - status = STATUS_DEVICE_SUCCESS; - } else { - status = STATUS_PAR_ERR; +static data_frame_tx_t *cmd_processor_set_ble_connect_key(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + if (length != BLE_PAIRING_KEY_LEN) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); + } + // Must be 6 ASCII characters, can only be 0-9. + bool is_valid_key = true; + for (uint8_t i = 0; i < BLE_PAIRING_KEY_LEN; i++) { + if (data[i] < '0' || data[i] > '9') { + is_valid_key = false; + break; } - } else { - status = STATUS_PAR_ERR; } - return data_frame_make(cmd, status, 0, NULL); + if (!is_valid_key) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); + } + // Key is valid, we can update to config + settings_set_ble_connect_key(data); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 0, NULL); } -data_frame_tx_t *cmd_processor_del_ble_all_bonds(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { +static data_frame_tx_t *cmd_processor_delete_all_ble_bonds(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { advertising_stop(); delete_bonds_all(); return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 0, NULL); @@ -839,13 +836,12 @@ data_frame_tx_t *cmd_processor_del_ble_all_bonds(uint16_t cmd, uint16_t status, * before reader run, reset reader and on antenna, * we must to wait some time, to init picc(power). */ -data_frame_tx_t *before_reader_run(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { +static data_frame_tx_t *before_reader_run(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { device_mode_t mode = get_device_mode(); - if (mode == DEVICE_MODE_READER) { - return NULL; - } else { + if (mode != DEVICE_MODE_READER) { return data_frame_make(cmd, STATUS_DEVICE_MODE_ERROR, 0, NULL); } + return NULL; } @@ -853,7 +849,7 @@ data_frame_tx_t *before_reader_run(uint16_t cmd, uint16_t status, uint16_t lengt * before reader run, reset reader and on antenna, * we must to wait some time, to init picc(power). */ -data_frame_tx_t *before_hf_reader_run(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { +static data_frame_tx_t *before_hf_reader_run(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { data_frame_tx_t *ret = before_reader_run(cmd, status, length, data); if (ret == NULL) { pcd_14a_reader_reset(); @@ -866,21 +862,31 @@ data_frame_tx_t *before_hf_reader_run(uint16_t cmd, uint16_t status, uint16_t le /** * after reader run, off antenna, to keep battery. */ -data_frame_tx_t *after_hf_reader_run(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { +static data_frame_tx_t *after_hf_reader_run(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { pcd_14a_reader_antenna_off(); return NULL; } #endif +// fct will be defined after m_data_cmd_map because we need to know its size +data_frame_tx_t *cmd_processor_get_device_capabilities(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data); + /** * (cmd -> processor) function map, the map struct is: * cmd code before process cmd processor after process */ static cmd_data_map_t m_data_cmd_map[] = { - { DATA_CMD_GET_APP_VERSION, NULL, cmd_processor_get_version, NULL }, + { DATA_CMD_GET_APP_VERSION, NULL, cmd_processor_get_app_version, NULL }, { DATA_CMD_CHANGE_DEVICE_MODE, NULL, cmd_processor_change_device_mode, NULL }, { DATA_CMD_GET_DEVICE_MODE, NULL, cmd_processor_get_device_mode, NULL }, + { DATA_CMD_SET_ACTIVE_SLOT, NULL, cmd_processor_set_active_slot, NULL }, + { DATA_CMD_SET_SLOT_TAG_TYPE, NULL, cmd_processor_set_slot_tag_type, NULL }, + { DATA_CMD_SET_SLOT_DATA_DEFAULT, NULL, cmd_processor_set_slot_data_default, NULL }, + { DATA_CMD_SET_SLOT_ENABLE, NULL, cmd_processor_set_slot_enable, NULL }, + { DATA_CMD_SET_SLOT_TAG_NICK, NULL, cmd_processor_set_slot_tag_nick, NULL }, + { DATA_CMD_GET_SLOT_TAG_NICK, NULL, cmd_processor_get_slot_tag_nick, NULL }, + { DATA_CMD_SLOT_DATA_CONFIG_SAVE, NULL, cmd_processor_slot_data_config_save, NULL }, { DATA_CMD_ENTER_BOOTLOADER, NULL, cmd_processor_enter_bootloader, NULL }, { DATA_CMD_GET_DEVICE_CHIP_ID, NULL, cmd_processor_get_device_chip_id, NULL }, { DATA_CMD_GET_DEVICE_ADDRESS, NULL, cmd_processor_get_device_address, NULL }, @@ -889,101 +895,78 @@ static cmd_data_map_t m_data_cmd_map[] = { { DATA_CMD_SET_ANIMATION_MODE, NULL, cmd_processor_set_animation_mode, NULL }, { DATA_CMD_GET_ANIMATION_MODE, NULL, cmd_processor_get_animation_mode, NULL }, { DATA_CMD_GET_GIT_VERSION, NULL, cmd_processor_get_git_version, NULL }, + { DATA_CMD_GET_ACTIVE_SLOT, NULL, cmd_processor_get_active_slot, NULL }, + { DATA_CMD_GET_SLOT_INFO, NULL, cmd_processor_get_slot_info, NULL }, + { DATA_CMD_WIPE_FDS, NULL, cmd_processor_wipe_fds, NULL }, + { DATA_CMD_GET_ENABLED_SLOTS, NULL, cmd_processor_get_enabled_slots, NULL }, + { DATA_CMD_DELETE_SLOT_SENSE_TYPE, NULL, cmd_processor_delete_slot_sense_type, NULL }, { DATA_CMD_GET_BATTERY_INFO, NULL, cmd_processor_get_battery_info, NULL }, { DATA_CMD_GET_BUTTON_PRESS_CONFIG, NULL, cmd_processor_get_button_press_config, NULL }, { DATA_CMD_SET_BUTTON_PRESS_CONFIG, NULL, cmd_processor_set_button_press_config, NULL }, { DATA_CMD_GET_LONG_BUTTON_PRESS_CONFIG, NULL, cmd_processor_get_long_button_press_config, NULL }, { DATA_CMD_SET_LONG_BUTTON_PRESS_CONFIG, NULL, cmd_processor_set_long_button_press_config, NULL }, - { DATA_CMD_GET_BLE_CONNECT_KEY_CONFIG, NULL, cmd_processor_get_ble_connect_key, NULL }, - { DATA_CMD_SET_BLE_CONNECT_KEY_CONFIG, NULL, cmd_processor_set_ble_connect_key, NULL }, - { DATA_CMD_DELETE_ALL_BLE_BONDS, NULL, cmd_processor_del_ble_all_bonds, NULL }, - { DATA_CMD_GET_DEVICE, NULL, cmd_processor_get_device, NULL }, - { DATA_CMD_GET_SETTINGS, NULL, cmd_processor_get_settings, NULL }, - { DATA_CMD_GET_DEVICE_CAPABILITIES, NULL, NULL, NULL }, + { DATA_CMD_GET_BLE_PAIRING_KEY, NULL, cmd_processor_get_ble_connect_key, NULL }, + { DATA_CMD_SET_BLE_PAIRING_KEY, NULL, cmd_processor_set_ble_connect_key, NULL }, + { DATA_CMD_DELETE_ALL_BLE_BONDS, NULL, cmd_processor_delete_all_ble_bonds, NULL }, + { DATA_CMD_GET_DEVICE_MODEL, NULL, cmd_processor_get_device_model, NULL }, + { DATA_CMD_GET_DEVICE_SETTINGS, NULL, cmd_processor_get_device_settings, NULL }, + { DATA_CMD_GET_DEVICE_CAPABILITIES, NULL, cmd_processor_get_device_capabilities, NULL }, { DATA_CMD_GET_BLE_PAIRING_ENABLE, NULL, cmd_processor_get_ble_pairing_enable, NULL }, { DATA_CMD_SET_BLE_PAIRING_ENABLE, NULL, cmd_processor_set_ble_pairing_enable, NULL }, #if defined(PROJECT_CHAMELEON_ULTRA) - { DATA_CMD_SCAN_14A_TAG, before_hf_reader_run, cmd_processor_14a_scan, after_hf_reader_run }, - { DATA_CMD_MF1_SUPPORT_DETECT, before_hf_reader_run, cmd_processor_detect_mf1_support, after_hf_reader_run }, - { DATA_CMD_MF1_NT_LEVEL_DETECT, before_hf_reader_run, cmd_processor_detect_mf1_nt_level, after_hf_reader_run }, - { DATA_CMD_MF1_DARKSIDE_DETECT, before_hf_reader_run, cmd_processor_detect_mf1_darkside, after_hf_reader_run }, + { DATA_CMD_HF14A_SCAN, before_hf_reader_run, cmd_processor_hf14a_scan, after_hf_reader_run }, + { DATA_CMD_MF1_DETECT_SUPPORT, before_hf_reader_run, cmd_processor_mf1_detect_support, after_hf_reader_run }, + { DATA_CMD_MF1_DETECT_PRNG, before_hf_reader_run, cmd_processor_mf1_detect_prng, after_hf_reader_run }, + { DATA_CMD_MF1_DETECT_DARKSIDE, before_hf_reader_run, cmd_processor_mf1_detect_darkside, after_hf_reader_run }, { DATA_CMD_MF1_DARKSIDE_ACQUIRE, before_hf_reader_run, cmd_processor_mf1_darkside_acquire, after_hf_reader_run }, - { DATA_CMD_MF1_NT_DIST_DETECT, before_hf_reader_run, cmd_processor_mf1_nt_distance, after_hf_reader_run }, + { DATA_CMD_MF1_DETECT_NT_DIST, before_hf_reader_run, cmd_processor_mf1_detect_nt_dist, after_hf_reader_run }, { DATA_CMD_MF1_NESTED_ACQUIRE, before_hf_reader_run, cmd_processor_mf1_nested_acquire, after_hf_reader_run }, - { DATA_CMD_MF1_CHECK_ONE_KEY_BLOCK, before_hf_reader_run, cmd_processor_mf1_auth_one_key_block, after_hf_reader_run }, + { DATA_CMD_MF1_AUTH_ONE_KEY_BLOCK, before_hf_reader_run, cmd_processor_mf1_auth_one_key_block, after_hf_reader_run }, { DATA_CMD_MF1_READ_ONE_BLOCK, before_hf_reader_run, cmd_processor_mf1_read_one_block, after_hf_reader_run }, { DATA_CMD_MF1_WRITE_ONE_BLOCK, before_hf_reader_run, cmd_processor_mf1_write_one_block, after_hf_reader_run }, - { DATA_CMD_SCAN_EM410X_TAG, before_reader_run, cmd_processor_em410x_scan, NULL }, - { DATA_CMD_WRITE_EM410X_TO_T5577, before_reader_run, cmd_processor_write_em410x_2_t57, NULL }, + { DATA_CMD_EM410X_SCAN, before_reader_run, cmd_processor_em410x_scan, NULL }, + { DATA_CMD_EM410X_WRITE_TO_T55XX, before_reader_run, cmd_processor_em410x_write_to_t55XX, NULL }, #endif - { DATA_CMD_SET_SLOT_ACTIVATED, NULL, cmd_processor_set_slot_activated, NULL }, - { DATA_CMD_SET_SLOT_TAG_TYPE, NULL, cmd_processor_set_slot_tag_type, NULL }, - { DATA_CMD_SET_SLOT_DATA_DEFAULT, NULL, cmd_processor_set_slot_data_default, NULL }, - { DATA_CMD_SET_SLOT_ENABLE, NULL, cmd_processor_set_slot_enable, NULL }, - { DATA_CMD_SLOT_DATA_CONFIG_SAVE, NULL, cmd_processor_slot_data_config_save, NULL }, - { DATA_CMD_GET_ACTIVE_SLOT, NULL, cmd_processor_get_activated_slot, NULL }, - { DATA_CMD_GET_SLOT_INFO, NULL, cmd_processor_get_slot_info, NULL }, - { DATA_CMD_WIPE_FDS, NULL, cmd_processor_wipe_fds, NULL }, - { DATA_CMD_GET_ENABLED_SLOTS, NULL, cmd_processor_get_enabled_slots, NULL }, - { DATA_CMD_DELETE_SLOT_SENSE_TYPE, NULL, cmd_processor_delete_slot_sense_type, NULL }, - - - - { DATA_CMD_SET_EM410X_EMU_ID, NULL, cmd_processor_set_em410x_emu_id, NULL }, - { DATA_CMD_GET_EM410X_EMU_ID, NULL, cmd_processor_get_em410x_emu_id, NULL }, - - { DATA_CMD_GET_MF1_DETECTION_STATUS, NULL, cmd_processor_get_mf1_detection_status, NULL }, - { DATA_CMD_SET_MF1_DETECTION_ENABLE, NULL, cmd_processor_set_mf1_detection_enable, NULL }, - { DATA_CMD_GET_MF1_DETECTION_COUNT, NULL, cmd_processor_get_mf1_detection_count, NULL }, - { DATA_CMD_GET_MF1_DETECTION_RESULT, NULL, cmd_processor_get_mf1_detection_log, NULL }, - { DATA_CMD_LOAD_MF1_EMU_BLOCK_DATA, NULL, cmd_processor_set_mf1_emulator_block, NULL }, - { DATA_CMD_READ_MF1_EMU_BLOCK_DATA, NULL, cmd_processor_get_mf1_emulator_block, NULL }, - { DATA_CMD_SET_MF1_ANTI_COLLISION_RES, NULL, cmd_processor_set_mf1_anti_collision_res, NULL }, - { DATA_CMD_GET_MF1_EMULATOR_CONFIG, NULL, cmd_processor_get_mf1_info, NULL }, - { DATA_CMD_GET_MF1_GEN1A_MODE, NULL, cmd_processor_get_mf1_gen1a_magic_mode, NULL }, - { DATA_CMD_SET_MF1_GEN1A_MODE, NULL, cmd_processor_set_mf1_gen1a_magic_mode, NULL }, - { DATA_CMD_GET_MF1_GEN2_MODE, NULL, cmd_processor_get_mf1_gen2_magic_mode, NULL }, - { DATA_CMD_SET_MF1_GEN2_MODE, NULL, cmd_processor_set_mf1_gen2_magic_mode, NULL }, - { DATA_CMD_GET_MF1_USE_FIRST_BLOCK_COLL, NULL, cmd_processor_get_mf1_use_coll_res, NULL }, - { DATA_CMD_SET_MF1_USE_FIRST_BLOCK_COLL, NULL, cmd_processor_set_mf1_use_coll_res, NULL }, - { DATA_CMD_GET_MF1_WRITE_MODE, NULL, cmd_processor_get_mf1_write_mode, NULL }, - { DATA_CMD_SET_MF1_WRITE_MODE, NULL, cmd_processor_set_mf1_write_mode, NULL }, - { DATA_CMD_GET_MF1_ANTI_COLL_DATA, NULL, cmd_processor_get_mf1_anti_coll_data, NULL }, - - { DATA_CMD_SET_SLOT_TAG_NICK, NULL, cmd_processor_set_slot_tag_nick_name, NULL }, - { DATA_CMD_GET_SLOT_TAG_NICK, NULL, cmd_processor_get_slot_tag_nick_name, NULL }, + { DATA_CMD_MF1_WRITE_EMU_BLOCK_DATA, NULL, cmd_processor_mf1_write_emu_block_data, NULL }, + { DATA_CMD_HF14A_SET_ANTI_COLL_DATA, NULL, cmd_processor_hf14a_set_anti_coll_data, NULL }, + + { DATA_CMD_MF1_SET_DETECTION_ENABLE, NULL, cmd_processor_mf1_set_detection_enable, NULL }, + { DATA_CMD_MF1_GET_DETECTION_COUNT, NULL, cmd_processor_mf1_get_detection_count, NULL }, + { DATA_CMD_MF1_GET_DETECTION_LOG, NULL, cmd_processor_mf1_get_detection_log, NULL }, + { DATA_CMD_MF1_GET_DETECTION_ENABLE, NULL, cmd_processor_mf1_get_detection_enable, NULL }, + { DATA_CMD_MF1_READ_EMU_BLOCK_DATA, NULL, cmd_processor_mf1_read_emu_block_data, NULL }, + { DATA_CMD_MF1_GET_EMULATOR_CONFIG, NULL, cmd_processor_mf1_get_emulator_config, NULL }, + { DATA_CMD_MF1_GET_GEN1A_MODE, NULL, cmd_processor_mf1_get_gen1a_mode, NULL }, + { DATA_CMD_MF1_SET_GEN1A_MODE, NULL, cmd_processor_mf1_set_gen1a_mode, NULL }, + { DATA_CMD_MF1_GET_GEN2_MODE, NULL, cmd_processor_mf1_get_gen2_mode, NULL }, + { DATA_CMD_MF1_SET_GEN2_MODE, NULL, cmd_processor_mf1_set_gen2_mode, NULL }, + { DATA_CMD_MF1_GET_BLOCK_ANTI_COLL_MODE, NULL, cmd_processor_mf1_get_block_anti_coll_mode, NULL }, + { DATA_CMD_MF1_SET_BLOCK_ANTI_COLL_MODE, NULL, cmd_processor_mf1_set_block_anti_coll_mode, NULL }, + { DATA_CMD_MF1_GET_WRITE_MODE, NULL, cmd_processor_mf1_get_write_mode, NULL }, + { DATA_CMD_MF1_SET_WRITE_MODE, NULL, cmd_processor_mf1_set_write_mode, NULL }, + { DATA_CMD_HF14A_GET_ANTI_COLL_DATA, NULL, cmd_processor_hf14a_get_anti_coll_data, NULL }, + + { DATA_CMD_EM410X_SET_EMU_ID, NULL, cmd_processor_em410x_set_emu_id, NULL }, + { DATA_CMD_EM410X_GET_EMU_ID, NULL, cmd_processor_em410x_get_emu_id, NULL }, }; - -data_frame_tx_t *cmd_processor_get_capabilities(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - size_t count = sizeof(m_data_cmd_map) / sizeof(m_data_cmd_map[0]); +data_frame_tx_t *cmd_processor_get_device_capabilities(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + size_t count = ARRAYLEN(m_data_cmd_map); uint16_t commands[count]; memset(commands, 0, count * sizeof(uint16_t)); for (size_t i = 0; i < count; i++) { - commands[i] = PP_HTONS(m_data_cmd_map[i].cmd); + commands[i] = U16HTONS(m_data_cmd_map[i].cmd); } - return data_frame_make(cmd, status, count * sizeof(uint16_t), (uint8_t *)commands); -} - - -void cmd_map_init() { - size_t count = sizeof(m_data_cmd_map) / sizeof(m_data_cmd_map[0]); - - for (size_t i = 0; i < count; i++) { - if (m_data_cmd_map[i].cmd == DATA_CMD_GET_DEVICE_CAPABILITIES) { - m_data_cmd_map[i].cmd_processor = cmd_processor_get_capabilities; - return; - } - } + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, count * sizeof(uint16_t), (uint8_t *)commands); } /** @@ -991,7 +974,7 @@ void cmd_map_init() { * * @param resp data */ -void auto_response_data(data_frame_tx_t *resp) { +static void auto_response_data(data_frame_tx_t *resp) { // TODO Please select the reply source automatically according to the message source, // and do not reply by checking the validity of the link layer by layer if (is_usb_working()) { @@ -1009,9 +992,6 @@ void auto_response_data(data_frame_tx_t *resp) { void on_data_frame_received(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { data_frame_tx_t *response = NULL; bool is_cmd_support = false; - // print info - NRF_LOG_INFO("Data frame: cmd = %02x, status = %02x, length = %d", cmd, status, length); - NRF_LOG_HEXDUMP_INFO(data, length); for (int i = 0; i < ARRAY_SIZE(m_data_cmd_map); i++) { if (m_data_cmd_map[i].cmd == cmd) { is_cmd_support = true; diff --git a/firmware/application/src/app_cmd.h b/firmware/application/src/app_cmd.h index 02b963e7..538c069d 100644 --- a/firmware/application/src/app_cmd.h +++ b/firmware/application/src/app_cmd.h @@ -15,6 +15,5 @@ typedef struct { } cmd_data_map_t; void on_data_frame_received(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data); -void cmd_map_init(); #endif diff --git a/firmware/application/src/app_main.c b/firmware/application/src/app_main.c index 030cd387..2d4620b4 100644 --- a/firmware/application/src/app_main.c +++ b/firmware/application/src/app_main.c @@ -771,7 +771,6 @@ static void ble_passkey_init(void) { */ int main(void) { hw_connect_init(); // Remember to initialize the pins first - cmd_map_init(); // Set function in CMD map for DATA_CMD_GET_DEVICE_CAPABILITIES fds_util_init(); // Initialize fds tool settings_load_config(); // Load settings from flash diff --git a/firmware/application/src/app_status.h b/firmware/application/src/app_status.h index 44ddc010..f0848279 100644 --- a/firmware/application/src/app_status.h +++ b/firmware/application/src/app_status.h @@ -15,17 +15,6 @@ #define HF_ERR_PARITY (0x07) // IC card parity error -///////////////////////////////////////////////////////////////////// -// MIFARE status -///////////////////////////////////////////////////////////////////// -#define DARKSIDE_CANT_FIXED_NT (0x20) // Darkside, the random number cannot be fixed, this situation may appear on the UID card -#define DARKSIDE_LUCK_AUTH_OK (0x21) // Darkside, the direct verification is successful, maybe the key is just empty -#define DARKSIDE_NACK_NO_SEND (0x22) // Darkside, the card does not respond to NACK, it may be a card that fixes Nack logic vulnerabilities -#define DARKSIDE_TAG_CHANGED (0x23) // Darkside, card switching in the process of running DARKSIDE, May is the two cards quickly switched -#define NESTED_TAG_IS_STATIC (0x24) // Nested, the random number of the card response is fixed -#define NESTED_TAG_IS_HARD (0x25) // Nested, the random number of the card response is unpredictable - - ///////////////////////////////////////////////////////////////////// // lf status ///////////////////////////////////////////////////////////////////// diff --git a/firmware/application/src/ble_main.c b/firmware/application/src/ble_main.c index 464caaa5..79cfb7ef 100644 --- a/firmware/application/src/ble_main.c +++ b/firmware/application/src/ble_main.c @@ -106,8 +106,8 @@ static ble_opt_t m_static_pin_option; * @details This function will set up the ble connect passkey. */ void set_ble_connect_key(uint8_t *key) { - static uint8_t passkey[BLE_CONNECT_KEY_LEN_MAX]; - memcpy(passkey, key, BLE_CONNECT_KEY_LEN_MAX); + static uint8_t passkey[BLE_PAIRING_KEY_LEN]; + memcpy(passkey, key, BLE_PAIRING_KEY_LEN); m_static_pin_option.gap_opt.passkey.p_passkey = passkey; // NRF_LOG_RAW_HEXDUMP_INFO(passkey, 6); APP_ERROR_CHECK(sd_ble_opt_set(BLE_GAP_OPT_PASSKEY, &m_static_pin_option)); @@ -331,7 +331,7 @@ static void services_init(void) { bas_init_obj.bl_cccd_wr_sec = SEC_OPEN; bas_init_obj.bl_report_rd_sec = SEC_OPEN; } - + err_code = ble_bas_init(&m_bas, &bas_init_obj); APP_ERROR_CHECK(err_code); } diff --git a/firmware/application/src/data_cmd.h b/firmware/application/src/data_cmd.h index f8595012..2bdd087c 100644 --- a/firmware/application/src/data_cmd.h +++ b/firmware/application/src/data_cmd.h @@ -10,7 +10,7 @@ #define DATA_CMD_GET_APP_VERSION (1000) #define DATA_CMD_CHANGE_DEVICE_MODE (1001) #define DATA_CMD_GET_DEVICE_MODE (1002) -#define DATA_CMD_SET_SLOT_ACTIVATED (1003) +#define DATA_CMD_SET_ACTIVE_SLOT (1003) #define DATA_CMD_SET_SLOT_TAG_TYPE (1004) #define DATA_CMD_SET_SLOT_DATA_DEFAULT (1005) #define DATA_CMD_SET_SLOT_ENABLE (1006) @@ -28,6 +28,7 @@ #define DATA_CMD_GET_ACTIVE_SLOT (1018) #define DATA_CMD_GET_SLOT_INFO (1019) #define DATA_CMD_WIPE_FDS (1020) + #define DATA_CMD_GET_ENABLED_SLOTS (1023) #define DATA_CMD_DELETE_SLOT_SENSE_TYPE (1024) #define DATA_CMD_GET_BATTERY_INFO (1025) @@ -35,11 +36,11 @@ #define DATA_CMD_SET_BUTTON_PRESS_CONFIG (1027) #define DATA_CMD_GET_LONG_BUTTON_PRESS_CONFIG (1028) #define DATA_CMD_SET_LONG_BUTTON_PRESS_CONFIG (1029) -#define DATA_CMD_SET_BLE_CONNECT_KEY_CONFIG (1030) -#define DATA_CMD_GET_BLE_CONNECT_KEY_CONFIG (1031) +#define DATA_CMD_SET_BLE_PAIRING_KEY (1030) +#define DATA_CMD_GET_BLE_PAIRING_KEY (1031) #define DATA_CMD_DELETE_ALL_BLE_BONDS (1032) -#define DATA_CMD_GET_DEVICE (1033) -#define DATA_CMD_GET_SETTINGS (1034) +#define DATA_CMD_GET_DEVICE_MODEL (1033) +#define DATA_CMD_GET_DEVICE_SETTINGS (1034) #define DATA_CMD_GET_DEVICE_CAPABILITIES (1035) #define DATA_CMD_GET_BLE_PAIRING_ENABLE (1036) #define DATA_CMD_SET_BLE_PAIRING_ENABLE (1037) @@ -53,14 +54,14 @@ // Range from 2000 -> 2999 // ****************************************************************** // -#define DATA_CMD_SCAN_14A_TAG (2000) -#define DATA_CMD_MF1_SUPPORT_DETECT (2001) -#define DATA_CMD_MF1_NT_LEVEL_DETECT (2002) -#define DATA_CMD_MF1_DARKSIDE_DETECT (2003) +#define DATA_CMD_HF14A_SCAN (2000) +#define DATA_CMD_MF1_DETECT_SUPPORT (2001) +#define DATA_CMD_MF1_DETECT_PRNG (2002) +#define DATA_CMD_MF1_DETECT_DARKSIDE (2003) #define DATA_CMD_MF1_DARKSIDE_ACQUIRE (2004) -#define DATA_CMD_MF1_NT_DIST_DETECT (2005) +#define DATA_CMD_MF1_DETECT_NT_DIST (2005) #define DATA_CMD_MF1_NESTED_ACQUIRE (2006) -#define DATA_CMD_MF1_CHECK_ONE_KEY_BLOCK (2007) +#define DATA_CMD_MF1_AUTH_ONE_KEY_BLOCK (2007) #define DATA_CMD_MF1_READ_ONE_BLOCK (2008) #define DATA_CMD_MF1_WRITE_ONE_BLOCK (2009) // @@ -72,8 +73,8 @@ // Range from 3000 -> 3999 // ****************************************************************** // -#define DATA_CMD_SCAN_EM410X_TAG (3000) -#define DATA_CMD_WRITE_EM410X_TO_T5577 (3001) +#define DATA_CMD_EM410X_SCAN (3000) +#define DATA_CMD_EM410X_WRITE_TO_T55XX (3001) // // ****************************************************************** @@ -83,25 +84,23 @@ // Range from 4000 -> 4999 // ****************************************************************** // -#define DATA_CMD_LOAD_MF1_EMU_BLOCK_DATA (4000) -#define DATA_CMD_SET_MF1_ANTI_COLLISION_RES (4001) -#define DATA_CMD_SET_MF1_ANTI_COLLISION_INFO (4002) -#define DATA_CMD_SET_MF1_ATS_RESOURCE (4003) -#define DATA_CMD_SET_MF1_DETECTION_ENABLE (4004) -#define DATA_CMD_GET_MF1_DETECTION_COUNT (4005) -#define DATA_CMD_GET_MF1_DETECTION_RESULT (4006) -#define DATA_CMD_GET_MF1_DETECTION_STATUS (4007) -#define DATA_CMD_READ_MF1_EMU_BLOCK_DATA (4008) -#define DATA_CMD_GET_MF1_EMULATOR_CONFIG (4009) -#define DATA_CMD_GET_MF1_GEN1A_MODE (4010) -#define DATA_CMD_SET_MF1_GEN1A_MODE (4011) -#define DATA_CMD_GET_MF1_GEN2_MODE (4012) -#define DATA_CMD_SET_MF1_GEN2_MODE (4013) -#define DATA_CMD_GET_MF1_USE_FIRST_BLOCK_COLL (4014) -#define DATA_CMD_SET_MF1_USE_FIRST_BLOCK_COLL (4015) -#define DATA_CMD_GET_MF1_WRITE_MODE (4016) -#define DATA_CMD_SET_MF1_WRITE_MODE (4017) -#define DATA_CMD_GET_MF1_ANTI_COLL_DATA (4018) +#define DATA_CMD_MF1_WRITE_EMU_BLOCK_DATA (4000) +#define DATA_CMD_HF14A_SET_ANTI_COLL_DATA (4001) +#define DATA_CMD_MF1_SET_DETECTION_ENABLE (4004) +#define DATA_CMD_MF1_GET_DETECTION_COUNT (4005) +#define DATA_CMD_MF1_GET_DETECTION_LOG (4006) +#define DATA_CMD_MF1_GET_DETECTION_ENABLE (4007) +#define DATA_CMD_MF1_READ_EMU_BLOCK_DATA (4008) +#define DATA_CMD_MF1_GET_EMULATOR_CONFIG (4009) +#define DATA_CMD_MF1_GET_GEN1A_MODE (4010) +#define DATA_CMD_MF1_SET_GEN1A_MODE (4011) +#define DATA_CMD_MF1_GET_GEN2_MODE (4012) +#define DATA_CMD_MF1_SET_GEN2_MODE (4013) +#define DATA_CMD_MF1_GET_BLOCK_ANTI_COLL_MODE (4014) +#define DATA_CMD_MF1_SET_BLOCK_ANTI_COLL_MODE (4015) +#define DATA_CMD_MF1_GET_WRITE_MODE (4016) +#define DATA_CMD_MF1_SET_WRITE_MODE (4017) +#define DATA_CMD_HF14A_GET_ANTI_COLL_DATA (4018) // // ****************************************************************** @@ -114,7 +113,7 @@ // // ****************************************************************** -#define DATA_CMD_SET_EM410X_EMU_ID (5000) -#define DATA_CMD_GET_EM410X_EMU_ID (5001) +#define DATA_CMD_EM410X_SET_EMU_ID (5000) +#define DATA_CMD_EM410X_GET_EMU_ID (5001) #endif diff --git a/firmware/application/src/rfid/nfctag/hf/nfc_mf1.c b/firmware/application/src/rfid/nfctag/hf/nfc_mf1.c index 129e7b9d..508f33f4 100644 --- a/firmware/application/src/rfid/nfctag/hf/nfc_mf1.c +++ b/firmware/application/src/rfid/nfctag/hf/nfc_mf1.c @@ -344,10 +344,11 @@ void append_mf1_auth_log_step1(bool isKeyB, bool isNested, uint8_t block, uint8_ } // Determine whether this card slot enables the detection log record if (m_tag_information->config.detection_enable) { - m_auth_log.logs[m_auth_log.count].cmd.is_key_b = isKeyB; - m_auth_log.logs[m_auth_log.count].cmd.block = block; - m_auth_log.logs[m_auth_log.count].cmd.is_nested = isNested; + m_auth_log.logs[m_auth_log.count].is_key_b = isKeyB; + m_auth_log.logs[m_auth_log.count].block = block; + m_auth_log.logs[m_auth_log.count].is_nested = isNested; memcpy(m_auth_log.logs[m_auth_log.count].uid, UID_BY_CASCADE_LEVEL, 4); +// m_auth_log.logs[m_auth_log.count].nt = U32HTONL(*(uint32_t *)nonce); memcpy(m_auth_log.logs[m_auth_log.count].nt, nonce, 4); } } @@ -363,6 +364,8 @@ void append_mf1_auth_log_step2(uint8_t *nr, uint8_t *ar) { } if (m_tag_information->config.detection_enable) { // Cache encryption information +// m_auth_log.logs[m_auth_log.count].nr = U32HTONL(*(uint32_t *)nr); +// m_auth_log.logs[m_auth_log.count].ar = U32HTONL(*(uint32_t *)ar); memcpy(m_auth_log.logs[m_auth_log.count].nr, nr, 4); memcpy(m_auth_log.logs[m_auth_log.count].ar, ar, 4); } @@ -388,7 +391,7 @@ void append_mf1_auth_log_step3(bool is_auth_success) { /** @brief MF1 obtain verification log * @param count: The statistics of the verification log */ -nfc_tag_mf1_auth_log_t *get_mf1_auth_log(uint32_t *count) { +nfc_tag_mf1_auth_log_t *mf1_get_auth_log(uint32_t *count) { // First pass the total number of logs verified by verified *count = m_auth_log.count; // Just return to the head pointer of the log number array diff --git a/firmware/application/src/rfid/nfctag/hf/nfc_mf1.h b/firmware/application/src/rfid/nfctag/hf/nfc_mf1.h index f07d0e46..d7ac34e6 100644 --- a/firmware/application/src/rfid/nfctag/hf/nfc_mf1.h +++ b/firmware/application/src/rfid/nfctag/hf/nfc_mf1.h @@ -2,6 +2,7 @@ #define NFC_MF1_H #include "nfc_14a.h" +#include "netdata.h" // Exchange space for time. // Fast simulate enable(Implement By ChameleonMini Repo) @@ -121,22 +122,23 @@ typedef struct { // MF1 label verification history typedef struct { // Basic information of verification - struct { - uint8_t block; - uint8_t is_key_b: 1; - uint8_t is_nested: 1; - // Airspace, occupying positions - uint8_t : 6; - } cmd; + uint8_t block; + uint8_t is_key_b: 1; + uint8_t is_nested: 1; + // padding to full byte + uint8_t : 6; // MFKEY32 necessary parameters uint8_t uid[4]; uint8_t nt[4]; uint8_t nr[4]; uint8_t ar[4]; -} nfc_tag_mf1_auth_log_t; + // uint32_t nt; + // uint32_t nr; + // uint32_t ar; +} PACKED nfc_tag_mf1_auth_log_t; -nfc_tag_mf1_auth_log_t *get_mf1_auth_log(uint32_t *count); +nfc_tag_mf1_auth_log_t *mf1_get_auth_log(uint32_t *count); int nfc_tag_mf1_data_loadcb(tag_specific_type_t type, tag_data_buffer_t *buffer); int nfc_tag_mf1_data_savecb(tag_specific_type_t type, tag_data_buffer_t *buffer); bool nfc_tag_mf1_data_factory(uint8_t slot, tag_specific_type_t tag_type); diff --git a/firmware/application/src/rfid/reader/hf/mf1_toolbox.c b/firmware/application/src/rfid/reader/hf/mf1_toolbox.c index f2c50684..25d21006 100644 --- a/firmware/application/src/rfid/reader/hf/mf1_toolbox.c +++ b/firmware/application/src/rfid/reader/hf/mf1_toolbox.c @@ -27,7 +27,7 @@ static picc_14a_tag_t *p_tag_info = &m_tag_info; * @retval : none * */ -void nonce_distance_notable(uint32_t *msb, uint32_t *lsb) { +static void nonce_distance(uint32_t *msb, uint32_t *lsb) { uint16_t x = 1, pos; uint8_t calc_ok = 0; @@ -61,12 +61,12 @@ void nonce_distance_notable(uint32_t *msb, uint32_t *lsb) { * false = hardened prng * */ -bool validate_prng_nonce_notable(uint32_t nonce) { +static bool check_lfsr_prng(uint32_t nonce) { // Give the initial coordinate value uint32_t msb = nonce >> 16; uint32_t lsb = nonce & 0xffff; //The coordinates are passed in direct operation, and the rumors are also indirectly spread by the passing parameters. - nonce_distance_notable(&msb, &lsb); + nonce_distance(&msb, &lsb); return ((65535 - msb + lsb) % 65535) == 16; } @@ -91,7 +91,7 @@ static inline void reset_radio_field_with_delay(void) { * @retval : The length of the card response data, this length is the length of the bit, not the length of Byte * */ -uint8_t send_cmd(struct Crypto1State *pcs, uint8_t encrypted, uint8_t cmd, uint8_t data, uint8_t *status, uint8_t *answer, uint8_t *answer_parity, uint16_t answer_max_bit) { +static uint8_t send_cmd(struct Crypto1State *pcs, uint8_t encrypted, uint8_t cmd, uint8_t data, uint8_t *status, uint8_t *answer, uint8_t *answer_parity, uint16_t answer_max_bit) { // Here we set directly to static static uint8_t pos; static uint16_t len; @@ -256,7 +256,7 @@ int authex(struct Crypto1State *pcs, uint32_t uid, uint8_t blockNo, uint8_t keyT * As a result, the CPU cannot complete the replay attack within the same time. * There is basically no solution in this situation. It is recommended to close the non -critical interruption and task scheduling outside this code */ -uint8_t darkside_select_nonces(picc_14a_tag_t *tag, uint8_t block, uint8_t keytype, uint32_t *nt) { +static uint8_t darkside_select_nonces(picc_14a_tag_t *tag, uint8_t block, uint8_t keytype, uint32_t *nt, mf1_darkside_status_t *darkside_status) { #define NT_COUNT 15 uint8_t tag_auth[4] = { keytype, block, 0x00, 0x00 }; uint8_t tag_resp[4] = { 0x00 }; @@ -315,12 +315,14 @@ uint8_t darkside_select_nonces(picc_14a_tag_t *tag, uint8_t block, uint8_t keyty // If it is not greater than 0, it means that the clock cannot be synchronized. if (max == 0) { NRF_LOG_INFO("Can't sync nt.\n"); - return DARKSIDE_CANT_FIXED_NT; + *darkside_status = DARKSIDE_CANT_FIX_NT; + return HF_TAG_OK; } // NT is fixed successfully, the one with the highest number of times we take out // NRF_LOG_INFO("Sync nt: %"PRIu32", max = %d\n", nt_list[m], max); if (nt) *nt = nt_list[m]; // Only when the caller needs to get NT + *darkside_status = DARKSIDE_OK; return HF_TAG_OK; } @@ -332,7 +334,8 @@ uint8_t darkside_select_nonces(picc_14a_tag_t *tag, uint8_t block, uint8_t keyty * */ uint8_t darkside_recover_key(uint8_t targetBlk, uint8_t targetTyp, - uint8_t firstRecover, uint8_t ntSyncMax, DarksideCore *dc) { + uint8_t firstRecover, uint8_t ntSyncMax, DarksideCore_t *dc, + mf1_darkside_status_t *darkside_status) { // Card information for fixed use static uint32_t uid_ori = 0; @@ -385,8 +388,8 @@ uint8_t darkside_recover_key(uint8_t targetBlk, uint8_t targetTyp, uid_ori = get_u32_tag_uid(p_tag_info); // Then you need to fix a random number that may appear - status = darkside_select_nonces(p_tag_info, targetBlk, targetTyp, &nt_ori); - if (status != HF_TAG_OK) { + status = darkside_select_nonces(p_tag_info, targetBlk, targetTyp, &nt_ori, darkside_status); + if ((status != HF_TAG_OK) || (*darkside_status != DARKSIDE_OK)) { //The fixed random number failed, and the next step cannot be performed return status; } @@ -398,7 +401,8 @@ uint8_t darkside_recover_key(uint8_t targetBlk, uint8_t targetTyp, par = par_low; if (uid_ori != uid_cur) { - return DARKSIDE_TAG_CHANGED; + *darkside_status = DARKSIDE_TAG_CHANGED; + return HF_TAG_OK; } } @@ -437,7 +441,8 @@ uint8_t darkside_recover_key(uint8_t targetBlk, uint8_t targetTyp, // In other words, this error has no chance to correct the random number if (++resync_count == ntSyncMax) { NRF_LOG_INFO("Can't fix nonce.\r\n"); - return DARKSIDE_CANT_FIXED_NT; + *darkside_status = DARKSIDE_CANT_FIX_NT; + return HF_TAG_OK; } // When the clock is not synchronized, the following operation is meaningless @@ -473,7 +478,8 @@ uint8_t darkside_recover_key(uint8_t targetBlk, uint8_t targetTyp, // did we get lucky and got our dummy key to be valid? // however we dont feed key w uid it the prng.. NRF_LOG_INFO("Auth Ok, you are so lucky!\n"); - return DARKSIDE_LUCK_AUTH_OK; + *darkside_status = DARKSIDE_LUCKY_AUTH_OK; + return HF_TAG_OK; } // Receive answer. This will be a 4 Bit NACK when the 8 parity bits are OK after decoding @@ -500,7 +506,8 @@ uint8_t darkside_recover_key(uint8_t targetBlk, uint8_t targetTyp, par++; if (par == 0) { // tried all 256 possible parities without success. Card doesn't send NACK. NRF_LOG_INFO("Card doesn't send NACK.\r\n"); - return DARKSIDE_NACK_NO_SEND; + *darkside_status = DARKSIDE_NO_NAK_SENT; + return HF_TAG_OK; } } else { // Why this? @@ -522,6 +529,7 @@ uint8_t darkside_recover_key(uint8_t targetBlk, uint8_t targetTyp, memcpy(dc->ar, mf_nr_ar + 4, sizeof(dc->ar)); // NRF_LOG_INFO("Darkside done!\n"); + *darkside_status = DARKSIDE_OK; return HF_TAG_OK; } @@ -539,19 +547,16 @@ void antenna_switch_delay(uint32_t delay_ms) { /** * @brief :Determine whether this card supports DARKSIDE attack -* @retval : If support, return hf_tag_ok, if it is not supported, -* Return to the results of abnormal results during the detection process: -* 1. DARKSIDE_CANT_FIXED_NT -* 2. DARKSIDE_NACK_NO_SEND -* 3. DARKSIDE_TAG_CHANGED +* @retval : If support, return hf_tag_ok and darkside_status = OK. If it is not supported, +* Return to the results of abnormal results during the detection process in darkside_status * Or other card -related communication errors, the most common is loss card HF_TAG_NO * */ -uint8_t check_darkside_support() { +uint8_t check_darkside_support(mf1_darkside_status_t *darkside_status) { // Instantiated parameter - DarksideCore dc; + DarksideCore_t dc; //Determine and return the result directly - return darkside_recover_key(0x03, PICC_AUTHENT1A, true, 0x15, &dc); + return darkside_recover_key(0x03, PICC_AUTHENT1A, true, 0x15, &dc, darkside_status); } /** @@ -594,7 +599,7 @@ uint8_t check_tag_response_nt(picc_14a_tag_t *tag, uint32_t *nt) { * Lost card hf_tag_no and wrong status hf_errstat * */ -uint8_t check_std_mifare_nt_support() { +uint8_t check_std_mifare_nt_support(bool *support) { uint32_t nt1 = 0; // Find card, search on the field @@ -603,7 +608,12 @@ uint8_t check_std_mifare_nt_support() { } // Get NT - return check_tag_response_nt(p_tag_info, &nt1); + uint8_t status = check_tag_response_nt(p_tag_info, &nt1); + if (status == HF_TAG_NO) { + return HF_TAG_NO; + } + *support = status == HF_TAG_OK; + return HF_TAG_OK; } /** @@ -612,7 +622,7 @@ uint8_t check_std_mifare_nt_support() { * If support, return nested_tag_is_static, if not support, * */ -uint8_t check_static_nested_support() { +uint8_t check_static_prng(bool *is_static) { uint32_t nt1, nt2; uint8_t status; @@ -639,29 +649,31 @@ uint8_t check_static_nested_support() { } // Detect whether the random number is static - if (nt1 == nt2) { - return NESTED_TAG_IS_STATIC; - } - + *is_static = (nt1 == nt2); return HF_TAG_OK; } /** -* @brief : Determine whether this card supports the most common, weaker, and easiest nested attack +* @brief : Determine whether this card supports the most common, weaker, and easiest nested attack, or static, or hardnested * @retval : critical result * */ -uint8_t check_weak_nested_support() { +uint8_t check_prng_type(mf1_prng_type_t *prng_type) { uint8_t status; uint32_t nt1; - status = check_static_nested_support(); + bool is_static; + status = check_static_prng(&is_static); // If the judgment process is found, it is found that the StaticNested detection cannot be completed // Then return the state directly, no need to perform the following judgment logic. if (status != HF_TAG_OK) { return status; } + if (is_static) { + *prng_type = PRNG_STATIC; + return HF_TAG_OK; + } // Non -Static card, you can continue to run down logic // ------------------------------------ @@ -682,17 +694,19 @@ uint8_t check_weak_nested_support() { } //Calculate the effectiveness of NT - if (validate_prng_nonce_notable(nt1)) { + if (check_lfsr_prng(nt1)) { // NRF_LOG_INFO("The tag support Nested\n"); - return HF_TAG_OK; + *prng_type = PRNG_WEAK; + } else { + // NRF_LOG_INFO("The tag support HardNested\n"); + // NT is unpredictable and invalid. + *prng_type = PRNG_HARD; } - // NRF_LOG_INFO("The tag support HardNested\n"); - // NT is unpredictable and invalid. // ------------------------------------ // end - return NESTED_TAG_IS_HARD; + return HF_TAG_OK; } /** @@ -702,12 +716,12 @@ uint8_t check_weak_nested_support() { * @retval : Distance value * */ -uint32_t measure_nonces(uint32_t from, uint32_t to) { +static uint32_t measure_nonces(uint32_t from, uint32_t to) { // Give the initial coordinate value uint32_t msb = from >> 16; uint32_t lsb = to >> 16; // The coordinates are passed in direct operation, and the rumors are also indirectly spread by the passing parameters. - nonce_distance_notable(&msb, &lsb); + nonce_distance(&msb, &lsb); return (65535 + lsb - msb) % 65535; } @@ -753,7 +767,7 @@ uint32_t measure_median(uint32_t *src, uint32_t length) { * @retval : Operating result * */ -uint8_t measure_distance(uint64_t u64Key, uint8_t block, uint8_t type, uint32_t *distance) { +static uint8_t measure_distance(uint64_t u64Key, uint8_t block, uint8_t type, uint32_t *distance) { struct Crypto1State mpcs = {0, 0}; struct Crypto1State *pcs = &mpcs; uint32_t distances[DIST_NR] = { 0x00 }; @@ -784,7 +798,8 @@ uint8_t measure_distance(uint64_t u64Key, uint8_t block, uint8_t type, uint32_t // If you really encounter the same NT, then it can only be explained that this card is a ST card for special firmware if (nt1 == nt2) { NRF_LOG_INFO("StaticNested: %08x vs %08x\n", nt1, nt2); - return NESTED_TAG_IS_STATIC; + *distance = 0; + return HF_TAG_OK; } // After the measurement is completed, store in the buffer distances[index++] = measure_nonces(nt1, nt2); @@ -808,7 +823,7 @@ uint8_t measure_distance(uint64_t u64Key, uint8_t block, uint8_t type, uint32_t * @retval : Successfully return hf_tag_ok, verify the unsuccessful return of the non -hf_tag_ok value * */ -uint8_t nested_recover_core(NestedCore *pnc, uint64_t keyKnown, uint8_t blkKnown, uint8_t typKnown, uint8_t targetBlock, uint8_t targetType) { +static uint8_t nested_recover_core(NestedCore_t *pnc, uint64_t keyKnown, uint8_t blkKnown, uint8_t typKnown, uint8_t targetBlock, uint8_t targetType) { struct Crypto1State mpcs = {0, 0}; struct Crypto1State *pcs = &mpcs; uint8_t status; @@ -854,7 +869,7 @@ uint8_t nested_recover_core(NestedCore *pnc, uint64_t keyKnown, uint8_t blkKnown * @retval :The attack returns hf_tag_ok, the attack is unsuccessful to return the non -hf_tag_ok value * */ -uint8_t nested_recover_key(uint64_t keyKnown, uint8_t blkKnown, uint8_t typKnown, uint8_t targetBlock, uint8_t targetType, NestedCore ncs[SETS_NR]) { +uint8_t nested_recover_key(uint64_t keyKnown, uint8_t blkKnown, uint8_t typKnown, uint8_t targetBlock, uint8_t targetType, NestedCore_t ncs[SETS_NR]) { uint8_t m, res; // all operations must be based on the card res = pcd_14a_reader_scan_auto(p_tag_info); @@ -887,29 +902,19 @@ uint8_t nested_recover_key(uint64_t keyKnown, uint8_t blkKnown, uint8_t typKnown * @retval : Operating status value * */ -uint8_t nested_distance_detect(uint8_t block, uint8_t type, uint8_t *key, NestedDist *nd) { +uint8_t nested_distance_detect(uint8_t block, uint8_t type, uint8_t *key, uint8_t *uid, uint32_t *distance) { uint8_t status = HF_TAG_OK; - uint32_t distance = 0; + *distance = 0; //Must ensure that there is a card on the court status = pcd_14a_reader_scan_auto(p_tag_info); if (status != HF_TAG_OK) { return status; } else { // At least the card exists, you can copy the UID to the buffer first - get_4byte_tag_uid(p_tag_info, nd->uid); + get_4byte_tag_uid(p_tag_info, uid); } // Get distance, prepare for the next attack - status = measure_distance( - bytes_to_num(key, 6), - block, - type, - &distance - ); - // Everything is normal, we need to put the distance value into the result - if (status == HF_TAG_OK) { - num_to_bytes(distance, 4, nd->distance); - } - return status; + return measure_distance(bytes_to_num(key, 6), block, type, distance); } /** diff --git a/firmware/application/src/rfid/reader/hf/mf1_toolbox.h b/firmware/application/src/rfid/reader/hf/mf1_toolbox.h index 01f86795..3d8496ee 100644 --- a/firmware/application/src/rfid/reader/hf/mf1_toolbox.h +++ b/firmware/application/src/rfid/reader/hf/mf1_toolbox.h @@ -6,7 +6,7 @@ #include #include #include - +#include "netdata.h" #define SETS_NR 2 // Using several sets of random number probes, at least two can ensure that there are two sets of random number combinations for intersection inquiries. The larger the value, the easier it is to succeed. #define DIST_NR 3 // The more distance the distance can accurately judge the communication stability of the current card @@ -18,18 +18,27 @@ #define AUTH_FIRST 0 #define AUTH_NESTED 2 -typedef struct { //Answer the distance parameters required for Nested attack - uint8_t uid[4]; //The U32 part of the UID part of this distance data - uint8_t distance[4]; //Unblocked explicitly random number -} NestedDist; - +typedef enum { + PRNG_STATIC = 0u, // the random number of the card response is fixed + PRNG_WEAK = 1u, // the random number of the card response is weak + PRNG_HARD = 2u, // the random number of the card response is unpredictable +} mf1_prng_type_t; typedef struct { //Answer the random number parameters required for Nested attack uint8_t nt1[4]; //Unblocked explicitly random number uint8_t nt2[4]; //Random number of nested verification encryption uint8_t par; //The puppet test of the communication process of nested verification encryption, only the "low 3 digits', that is, the right 3 -} NestedCore; +} NestedCore_t; + +typedef enum { + DARKSIDE_OK = 0u, // normal process + DARKSIDE_CANT_FIX_NT = 1u, // the random number cannot be fixed, this situation may appear on some UID card + DARKSIDE_LUCKY_AUTH_OK = 2u, // the direct authentification is successful, maybe the key is just the default one + DARKSIDE_NO_NAK_SENT = 3u, // the card does not respond to NACK, it may be a card that fixes Nack logic vulnerabilities + DARKSIDE_TAG_CHANGED = 4u, // card swap while running DARKSIDE +} mf1_darkside_status_t; +// this struct is also used in the fw/cli protocol, therefore PACKED typedef struct { uint8_t uid[4]; uint8_t nt[4]; @@ -37,7 +46,7 @@ typedef struct { uint8_t ks_list[8]; uint8_t nr[4]; uint8_t ar[4]; -} DarksideCore; +} PACKED DarksideCore_t; #ifdef __cplusplus @@ -50,13 +59,15 @@ uint8_t darkside_recover_key( uint8_t targetTyp, uint8_t firstRecover, uint8_t ntSyncMax, - DarksideCore *dc + DarksideCore_t *dc, + mf1_darkside_status_t *darkside_status ); uint8_t nested_distance_detect( uint8_t block, uint8_t type, uint8_t *key, - NestedDist *nd + uint8_t *uid, + uint32_t *distance ); uint8_t nested_recover_key( uint64_t keyKnown, @@ -64,11 +75,11 @@ uint8_t nested_recover_key( uint8_t typKnown, uint8_t targetBlock, uint8_t targetType, - NestedCore ncs[SETS_NR] + NestedCore_t ncs[SETS_NR] ); -uint8_t check_darkside_support(void); -uint8_t check_weak_nested_support(void); -uint8_t check_std_mifare_nt_support(void); +uint8_t check_darkside_support(mf1_darkside_status_t *darkside_status); +uint8_t check_prng_type(mf1_prng_type_t *type); +uint8_t check_std_mifare_nt_support(bool *support); void antenna_switch_delay(uint32_t delay_ms); uint8_t auth_key_use_522_hw(uint8_t block, uint8_t type, uint8_t *key); diff --git a/firmware/application/src/rfid/reader/hf/rc522.h b/firmware/application/src/rfid/reader/hf/rc522.h index be1493f7..2c4d00f2 100644 --- a/firmware/application/src/rfid/reader/hf/rc522.h +++ b/firmware/application/src/rfid/reader/hf/rc522.h @@ -6,6 +6,7 @@ #include #include #include +#include "netdata.h" /* * rC522CommandWord @@ -151,13 +152,14 @@ #define U8ARR_BIT_LEN(src) ((sizeof(src)) * (8)) // basicStructurePackagingOfLabelInformation +// this struct is also used in the fw/cli protocol, therefore PACKED typedef struct { uint8_t uid[10]; // theByteArrayOfTheCardNumber,TheLongest10Byte uint8_t uid_len; // theLengthOfTheCardNumber uint8_t cascade; // theAntiCollisionLevelValueIs1Representation 4Byte,2Represents7Byte,3Means10Byte uint8_t sak; // chooseToConfirm uint8_t atqa[2]; // requestResponse -} picc_14a_tag_t; +} PACKED picc_14a_tag_t; #ifdef __cplusplus extern "C" { diff --git a/firmware/application/src/settings.c b/firmware/application/src/settings.c index ddf72d96..638e9b17 100644 --- a/firmware/application/src/settings.c +++ b/firmware/application/src/settings.c @@ -44,7 +44,7 @@ void settings_init_button_long_press_config(void) { // add on version4 void settings_init_ble_connect_key_config(void) { - uint8_t p_key_u8[] = DEFAULT_BLE_CONNECT_KEY; + uint8_t p_key_u8[] = DEFAULT_BLE_PAIRING_KEY; settings_set_ble_connect_key(p_key_u8); } @@ -276,7 +276,7 @@ uint8_t *settings_get_ble_connect_key(void) { * @param key Ble connect key for your device */ void settings_set_ble_connect_key(uint8_t *key) { - memcpy(config.ble_connect_key, key, BLE_CONNECT_KEY_LEN_MAX); + memcpy(config.ble_connect_key, key, BLE_PAIRING_KEY_LEN); } void settings_set_ble_pairing_enable(bool enable) { diff --git a/firmware/application/src/settings.h b/firmware/application/src/settings.h index 1a26bf17..adfd3cd1 100644 --- a/firmware/application/src/settings.h +++ b/firmware/application/src/settings.h @@ -6,8 +6,8 @@ #include "utils.h" #define SETTINGS_CURRENT_VERSION 5 -#define BLE_CONNECT_KEY_LEN_MAX 6 -#define DEFAULT_BLE_CONNECT_KEY "123456" // length must == 6 +#define BLE_PAIRING_KEY_LEN 6 +#define DEFAULT_BLE_PAIRING_KEY "123456" // length must == 6 typedef enum { SettingsAnimationModeFull = 0U, diff --git a/firmware/application/src/utils/dataframe.c b/firmware/application/src/utils/dataframe.c index 0b5c9619..15a088e6 100644 --- a/firmware/application/src/utils/dataframe.c +++ b/firmware/application/src/utils/dataframe.c @@ -1,6 +1,5 @@ #include "dataframe.h" -#include "hex_utils.h" - +#include "netdata.h" #define NRF_LOG_MODULE_NAME data_frame #include "nrf_log.h" @@ -8,40 +7,26 @@ #include "nrf_log_default_backends.h" NRF_LOG_MODULE_REGISTER(); - -/* - * ********************************************************************************************************************************* - * Variable length data frame format - * Designed by proxgrind - * Date: 20221205 - * - * 0 1 2 3 45 6 7 8 8 + n 8 + n + 1 - * SOF(1byte) LRC(1byte) CMD(2byte) Status(2byte) Data Length(2byte) Frame Head LRC(1byte) Data(length) Frame All LRC(1byte) - * 0x11 0xEF cmd(u16) status(u16) length(u16) lrc(u8) data(u8*) lrc(u8) - * - * The data length max is 512, frame length max is 1 + 1 + 2 + 2 + 2 + 1 + n + 1 = (10 + n) - * So, one frame will than 10 byte. - * ********************************************************************************************************************************* - */ - -#define DATA_PACK_TRANSMISSION_ON 0x11 -#define DATA_LRC_CUT(val) ((uint8_t)(0x100 - val)) - - -static uint8_t m_data_rx_buffer[DATA_PACK_MAX_DATA_LENGTH + DATA_PACK_BASE_LENGTH]; -static uint8_t m_data_tx_buffer[DATA_PACK_MAX_DATA_LENGTH + DATA_PACK_BASE_LENGTH]; +static netdata_frame_raw_t m_netdata_frame_rx_buf; +static netdata_frame_raw_t m_netdata_frame_tx_buf; +static data_frame_tx_t m_frame_tx_buf_info = { + .buffer = (uint8_t *) &m_netdata_frame_tx_buf, // default buffer +}; static uint16_t m_data_rx_position = 0; -static uint8_t m_data_rx_lrc = 0; static uint16_t m_data_cmd; static uint16_t m_data_status; static uint16_t m_data_len; static uint8_t *m_data_buffer; static volatile bool m_data_completed = false; static data_frame_cbk_t m_frame_process_cbk = NULL; -static data_frame_tx_t m_frame_tx_buf_info = { - .buffer = m_data_tx_buffer, // default buffer -}; +static uint8_t compute_lrc(uint8_t *buf, uint16_t bufsize) { + uint8_t lrc = 0x00; + for (uint16_t i = 0; i < bufsize; i++) { + lrc += buf[i]; + } + return 0x100 - lrc; +} /** * @brief: create a packet, put the created data packet into the buffer, and wait for the post to set up a non busy state @@ -50,38 +35,41 @@ static data_frame_tx_t m_frame_tx_buf_info = { * @param length: answerDataLength * @param data: answerData */ -data_frame_tx_t *data_frame_make(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - uint8_t lrc_tx = 0x00; - uint16_t i, j; +data_frame_tx_t *data_frame_make(uint16_t cmd, uint16_t status, uint16_t data_length, uint8_t *data) { + if (data_length > 0 && data == NULL) { + NRF_LOG_ERROR("data_frame_make error, null pointer."); + return NULL; + } + if (data_length > 512) { + NRF_LOG_ERROR("data_frame_make error, too much data."); + return NULL; + } + NRF_LOG_INFO("TX Data frame: cmd = 0x%04x (%i), status = 0x%04x, length = %d%s", cmd, cmd, status, data_length, data_length > 0 ? ", data =" : ""); + if (data_length > 0) { + NRF_LOG_HEXDUMP_INFO(data, data_length); + } + + netdata_frame_postamble_t *tx_post = (netdata_frame_postamble_t *)((uint8_t *)&m_netdata_frame_tx_buf + sizeof(netdata_frame_preamble_t) + data_length); // sof - m_frame_tx_buf_info.buffer[0] = DATA_PACK_TRANSMISSION_ON; + m_netdata_frame_tx_buf.pre.sof = NETDATA_FRAME_SOF; // sof lrc - lrc_tx += m_frame_tx_buf_info.buffer[0]; - m_frame_tx_buf_info.buffer[1] = DATA_LRC_CUT(lrc_tx); - lrc_tx += m_frame_tx_buf_info.buffer[1]; + m_netdata_frame_tx_buf.pre.lrc1 = compute_lrc((uint8_t *)&m_netdata_frame_tx_buf.pre, offsetof(netdata_frame_preamble_t, lrc1)); // cmd - num_to_bytes(cmd, 2, &m_frame_tx_buf_info.buffer[2]); + m_netdata_frame_tx_buf.pre.cmd = U16HTONS(cmd); // status - num_to_bytes(status, 2, &m_frame_tx_buf_info.buffer[4]); - // length - num_to_bytes(length, 2, &m_frame_tx_buf_info.buffer[6]); + m_netdata_frame_tx_buf.pre.status = U16HTONS(status); + // data_length + m_netdata_frame_tx_buf.pre.len = U16HTONS(data_length); // head lrc - for (i = 2; i < 8; i++) { - lrc_tx += m_frame_tx_buf_info.buffer[i]; - } - m_frame_tx_buf_info.buffer[8] = DATA_LRC_CUT(lrc_tx); - lrc_tx += m_frame_tx_buf_info.buffer[8]; + m_netdata_frame_tx_buf.pre.lrc2 = compute_lrc((uint8_t *)&m_netdata_frame_tx_buf.pre, offsetof(netdata_frame_preamble_t, lrc2)); // data - if (length > 0 && data != NULL) { - for (i = 9, j = 0; j < length; i++, j++) { - m_frame_tx_buf_info.buffer[i] = data[j]; - lrc_tx += m_frame_tx_buf_info.buffer[i]; - } + if (data_length > 0) { + memcpy(&m_netdata_frame_tx_buf.data, data, data_length); } // length out. - m_frame_tx_buf_info.length = (length + DATA_PACK_BASE_LENGTH); + m_frame_tx_buf_info.length = (sizeof(netdata_frame_preamble_t) + data_length + sizeof(netdata_frame_postamble_t)); // data all lrc - m_data_tx_buffer[m_frame_tx_buf_info.length - 1] = DATA_LRC_CUT(lrc_tx);; + tx_post->lrc3 = compute_lrc((uint8_t *)&m_netdata_frame_tx_buf.data, data_length); return (&m_frame_tx_buf_info); } @@ -90,7 +78,6 @@ data_frame_tx_t *data_frame_make(uint16_t cmd, uint16_t status, uint16_t length, */ void data_frame_reset(void) { m_data_rx_position = 0; - m_data_rx_lrc = 0; } /** @@ -105,7 +92,7 @@ void data_frame_receive(uint8_t *data, uint16_t length) { return; } // buffer overflow - if (m_data_rx_position + length >= sizeof(m_data_rx_buffer)) { + if (m_data_rx_position + length >= sizeof(m_netdata_frame_rx_buf)) { NRF_LOG_ERROR("Data frame wait overflow."); data_frame_reset(); return; @@ -113,50 +100,52 @@ void data_frame_receive(uint8_t *data, uint16_t length) { // frame process for (int i = 0; i < length; i++) { // copy to buffer - m_data_rx_buffer[m_data_rx_position] = data[i]; - if (m_data_rx_position < 2) { // start of frame - if (m_data_rx_position == 0) { - if (m_data_rx_buffer[m_data_rx_position] != DATA_PACK_TRANSMISSION_ON) { - // not sof byte - NRF_LOG_ERROR("Data frame no sof byte."); - data_frame_reset(); - return; - } + ((uint8_t *)(&m_netdata_frame_rx_buf))[m_data_rx_position] = data[i]; + if (m_data_rx_position == offsetof(netdata_frame_preamble_t, sof)) { + if (m_netdata_frame_rx_buf.pre.sof != NETDATA_FRAME_SOF) { + // not sof byte + NRF_LOG_ERROR("Data frame no sof byte."); + data_frame_reset(); + return; } - if (m_data_rx_position == 1) { - if (m_data_rx_buffer[m_data_rx_position] != DATA_LRC_CUT(m_data_rx_lrc)) { - // not sof lrc byte - NRF_LOG_ERROR("Data frame sof lrc error."); - data_frame_reset(); - return; - } + } else if (m_data_rx_position == offsetof(netdata_frame_preamble_t, lrc1)) { + if (m_netdata_frame_rx_buf.pre.lrc1 != compute_lrc((uint8_t *)&m_netdata_frame_rx_buf.pre, offsetof(netdata_frame_preamble_t, lrc1))) { + // not sof lrc byte + NRF_LOG_ERROR("Data frame sof lrc error."); + data_frame_reset(); + return; } - } else if (m_data_rx_position == 8) { // frame head lrc - if (m_data_rx_buffer[m_data_rx_position] != DATA_LRC_CUT(m_data_rx_lrc)) { + } else if (m_data_rx_position == offsetof(netdata_frame_preamble_t, lrc2)) { // frame head lrc + if (m_netdata_frame_rx_buf.pre.lrc2 != compute_lrc((uint8_t *)&m_netdata_frame_rx_buf.pre, offsetof(netdata_frame_preamble_t, lrc2))) { // frame head lrc error NRF_LOG_ERROR("Data frame head lrc error."); data_frame_reset(); return; } // frame head complete, cache info - m_data_cmd = bytes_to_num(&m_data_rx_buffer[2], 2); - m_data_status = bytes_to_num(&m_data_rx_buffer[4], 2); - m_data_len = bytes_to_num(&m_data_rx_buffer[6], 2); + m_data_cmd = U16NTOHS(m_netdata_frame_rx_buf.pre.cmd); + m_data_status = U16NTOHS(m_netdata_frame_rx_buf.pre.status); + m_data_len = U16NTOHS(m_netdata_frame_rx_buf.pre.len); NRF_LOG_INFO("Data frame data length %d.", m_data_len); // check data length - if (m_data_len > DATA_PACK_MAX_DATA_LENGTH) { - NRF_LOG_ERROR("Data frame data length too than of max."); + if (m_data_len > NETDATA_MAX_DATA_LENGTH) { + NRF_LOG_ERROR("Data frame data length larger than max."); data_frame_reset(); return; } - } else if (m_data_rx_position > 8) { // frame data + } else if (m_data_rx_position >= offsetof(netdata_frame_raw_t, data)) { // frame data // check all data ready. - if (m_data_rx_position == (8 + m_data_len + 1)) { - if (m_data_rx_buffer[m_data_rx_position] == DATA_LRC_CUT(m_data_rx_lrc)) { + if (m_data_rx_position == (sizeof(netdata_frame_preamble_t) + m_data_len)) { + netdata_frame_postamble_t *rx_post = (netdata_frame_postamble_t *)((uint8_t *)&m_netdata_frame_rx_buf + sizeof(netdata_frame_preamble_t) + m_data_len); + if (rx_post->lrc3 == compute_lrc((uint8_t *)&m_netdata_frame_rx_buf.data, m_data_len)) { // ok, lrc for data is check success. // and we are receive completed - m_data_buffer = m_data_len > 0 ? &m_data_rx_buffer[9] : NULL; + m_data_buffer = m_data_len > 0 ? (uint8_t *)&m_netdata_frame_rx_buf.data : NULL; m_data_completed = true; + NRF_LOG_INFO("RX Data frame: cmd = 0x%04x (%i), status = 0x%04x, length = %d%s", m_data_cmd, m_data_cmd, m_data_status, m_data_len, m_data_len > 0 ? ", data =" : ""); + if (m_data_len > 0) { + NRF_LOG_HEXDUMP_INFO(m_data_buffer, m_data_len); + } } else { // data frame lrc error NRF_LOG_ERROR("Data frame finally lrc error."); @@ -165,8 +154,6 @@ void data_frame_receive(uint8_t *data, uint16_t length) { return; } } - // calculate lrc - m_data_rx_lrc += data[i]; // index update m_data_rx_position++; } diff --git a/firmware/application/src/utils/dataframe.h b/firmware/application/src/utils/dataframe.h index d0e22f5d..87dba08b 100644 --- a/firmware/application/src/utils/dataframe.h +++ b/firmware/application/src/utils/dataframe.h @@ -1,23 +1,18 @@ -#ifndef DATA_PACK_H -#define DATA_PACK_H +#ifndef DATAFRAME_H +#define DATAFRAME_H #include #include - -#define DATA_PACK_MAX_DATA_LENGTH 512 -#define DATA_PACK_BASE_LENGTH 10 - - // Data frame process callback typedef void (*data_frame_cbk_t)(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data); + // TX buffer typedef struct { uint8_t *const buffer; uint16_t length; } data_frame_tx_t; - void data_frame_receive(uint8_t *data, uint16_t length); void data_frame_process(void); void on_data_frame_complete(data_frame_cbk_t callback); @@ -30,4 +25,4 @@ data_frame_tx_t *data_frame_make( ); -#endif +#endif // DATAFRAME_H diff --git a/firmware/application/src/utils/netdata.h b/firmware/application/src/utils/netdata.h new file mode 100644 index 00000000..b9451ff5 --- /dev/null +++ b/firmware/application/src/utils/netdata.h @@ -0,0 +1,65 @@ +#ifndef NETDATA_H +#define NETDATA_H + +#include +#include + +#define PACKED __attribute__((packed)) + +#ifndef ARRAYLEN +# define ARRAYLEN(x) (sizeof(x)/sizeof((x)[0])) +#endif + +// nrf52840 platform is little endian +#define U16HTONS(x) ((uint16_t)((((x) & (uint16_t)0x00ffU) << 8) | (((x) & (uint16_t)0xff00U) >> 8))) +#define U16NTOHS(x) U16HTONS(x) +#define U32HTONL(x) ((((x) & (uint32_t)0x000000ffUL) << 24) | \ + (((x) & (uint32_t)0x0000ff00UL) << 8) | \ + (((x) & (uint32_t)0x00ff0000UL) >> 8) | \ + (((x) & (uint32_t)0xff000000UL) >> 24)) +#define U32NTOHL(x) U32HTONL(x) + +#define NETDATA_MAX_DATA_LENGTH 512 + +/* + * ********************************************************************************************************************************* + * Variable length data frame format + * Designed by proxgrind + * Date: 20221205 + * + * 0 1 2 3 45 6 7 8 8 + n 8 + n + 1 + * SOF(1byte) LRC(1byte) CMD(2byte) Status(2byte) Data Length(2byte) Frame Head LRC(1byte) Data(length) Frame All LRC(1byte) + * 0x11 0xEF cmd(u16) status(u16) length(u16) lrc(u8) data(u8*) lrc(u8) + * + * The data length max is 512, frame length is 1 + 1 + 2 + 2 + 2 + 1 + n + 1 = (10 + n) + * So, one frame will be between 10 and 522 bytes. + * ********************************************************************************************************************************* + */ + +// data frame preamble as sent from/to the client, Network byte order. + +typedef struct { + uint8_t sof; + uint8_t lrc1; + uint16_t cmd; + uint16_t status; + uint16_t len; + uint8_t lrc2; +} PACKED netdata_frame_preamble_t; + +#define NETDATA_FRAME_SOF 0x11 + +typedef struct { + uint8_t lrc3; +} PACKED netdata_frame_postamble_t; + +// For reception and CRC check +typedef struct { + netdata_frame_preamble_t pre; + uint8_t data[NETDATA_MAX_DATA_LENGTH]; + netdata_frame_postamble_t foopost; // Probably not at that offset! +} PACKED netdata_frame_raw_t; + +// Command-specific structs are defined in their respective cmd_processor handlers in app_cmd.c + +#endif /* NETDATA_H */ diff --git a/firmware/common/hw_connect.c b/firmware/common/hw_connect.c index bb86bd54..7d2d59e4 100644 --- a/firmware/common/hw_connect.c +++ b/firmware/common/hw_connect.c @@ -107,9 +107,6 @@ void hw_connect_init(void) { #if defined(PROJECT_CHAMELEON_ULTRA) if (m_hw_ver == 1) { LED_FIELD = (NRF_GPIO_PIN_MAP(1, 1)); - LED_R = (NRF_GPIO_PIN_MAP(0, 24)); - LED_G = (NRF_GPIO_PIN_MAP(0, 22)); - LED_B = (NRF_GPIO_PIN_MAP(1, 0)); LED_1 = (NRF_GPIO_PIN_MAP(0, 20)); LED_2 = (NRF_GPIO_PIN_MAP(0, 17)); LED_3 = (NRF_GPIO_PIN_MAP(0, 15)); @@ -118,15 +115,26 @@ void hw_connect_init(void) { LED_6 = (NRF_GPIO_PIN_MAP(1, 9)); LED_7 = (NRF_GPIO_PIN_MAP(0, 8)); LED_8 = (NRF_GPIO_PIN_MAP(0, 6)); + LED_R = (NRF_GPIO_PIN_MAP(0, 24)); + LED_G = (NRF_GPIO_PIN_MAP(0, 22)); + LED_B = (NRF_GPIO_PIN_MAP(1, 0)); RGB_LIST_NUM = 8; RGB_CTRL_NUM = 3; - LF_ANT_DRIVER = (NRF_GPIO_PIN_MAP(0, 31)); - LF_OA_OUT = (NRF_GPIO_PIN_MAP(0, 29)); LF_MOD = (NRF_GPIO_PIN_MAP(1, 13)); LF_RSSI_PIN = (NRF_GPIO_PIN_MAP(0, 2)); LF_RSSI = NRF_LPCOMP_INPUT_0; + BUTTON_1 = (NRF_GPIO_PIN_MAP(1, 2)); + BUTTON_2 = (NRF_GPIO_PIN_MAP(0, 26)); + + BAT_SENSE_PIN = (NRF_GPIO_PIN_MAP(0, 4)); + BAT_SENSE = NRF_SAADC_INPUT_AIN2; + + // Ultra only + LF_ANT_DRIVER = (NRF_GPIO_PIN_MAP(0, 31)); + LF_OA_OUT = (NRF_GPIO_PIN_MAP(0, 29)); + HF_SPI_SELECT = (NRF_GPIO_PIN_MAP(1, 6)); HF_SPI_MISO = (NRF_GPIO_PIN_MAP(0, 11)); HF_SPI_MOSI = (NRF_GPIO_PIN_MAP(1, 7)); @@ -134,12 +142,6 @@ void hw_connect_init(void) { HF_ANT_SEL = (NRF_GPIO_PIN_MAP(1, 10)); READER_POWER = (NRF_GPIO_PIN_MAP(1, 15)); - - BUTTON_2 = (NRF_GPIO_PIN_MAP(0, 26)); - BUTTON_1 = (NRF_GPIO_PIN_MAP(1, 2)); - - BAT_SENSE_PIN = (NRF_GPIO_PIN_MAP(0, 4)); - BAT_SENSE = NRF_SAADC_INPUT_AIN2; } #endif diff --git a/firmware/common/lwip_def.h b/firmware/common/lwip_def.h deleted file mode 100644 index 5559a082..00000000 --- a/firmware/common/lwip_def.h +++ /dev/null @@ -1,142 +0,0 @@ -/** - * @file - * various utility macros - */ - -/* - * Copyright (c) 2001-2004 Swedish Institute of Computer Science. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, - * are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * 3. The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT - * SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT - * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS - * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING - * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY - * OF SUCH DAMAGE. - * - * This file is part of the lwIP TCP/IP stack. - * - * Author: Adam Dunkels - * - */ - -/** - * @defgroup perf Performance measurement - * @ingroup sys_layer - * All defines related to this section must not be placed in lwipopts.h, - * but in arch/perf.h! - * Measurement calls made throughout lwip, these can be defined to nothing. - * - PERF_START: start measuring something. - * - PERF_STOP(x): stop measuring something, and record the result. - */ - -#ifndef LWIP_HDR_DEF_H -#define LWIP_HDR_DEF_H - -/* arch.h might define NULL already */ -#define PERF_START /* null definition */ -#define PERF_STOP(x) /* null definition */ - -#ifdef __cplusplus -extern "C" { -#endif - -#define LWIP_MAX(x , y) (((x) > (y)) ? (x) : (y)) -#define LWIP_MIN(x , y) (((x) < (y)) ? (x) : (y)) - -/* Get the number of entries in an array ('x' must NOT be a pointer!) */ -#define LWIP_ARRAYSIZE(x) (sizeof(x)/sizeof((x)[0])) - -/** Create uint32_t value from bytes */ -#define LWIP_MAKEU32(a,b,c,d) (((uint32_t)((a) & 0xff) << 24) | \ - ((uint32_t)((b) & 0xff) << 16) | \ - ((uint32_t)((c) & 0xff) << 8) | \ - (uint32_t)((d) & 0xff)) - -#ifndef NULL -#ifdef __cplusplus -#define NULL 0 -#else -#define NULL ((void *)0) -#endif -#endif - -#if BYTE_ORDER == BIG_ENDIAN -#define lwip_htons(x) ((uint16_t)(x)) -#define lwip_ntohs(x) ((uint16_t)(x)) -#define lwip_htonl(x) ((uint32_t)(x)) -#define lwip_ntohl(x) ((uint32_t)(x)) -#define PP_HTONS(x) ((uint16_t)(x)) -#define PP_NTOHS(x) ((uint16_t)(x)) -#define PP_HTONL(x) ((uint32_t)(x)) -#define PP_NTOHL(x) ((uint32_t)(x)) -#else /* BYTE_ORDER != BIG_ENDIAN */ -#ifndef lwip_htons -uint16_t lwip_htons(uint16_t x); -#endif -#define lwip_ntohs(x) lwip_htons(x) - -#ifndef lwip_htonl -uint32_t lwip_htonl(uint32_t x); -#endif -#define lwip_ntohl(x) lwip_htonl(x) - -/* These macros should be calculated by the preprocessor and are used - with compile-time constants only (so that there is no little-endian - overhead at runtime). */ -#define PP_HTONS(x) ((uint16_t)((((x) & (uint16_t)0x00ffU) << 8) | (((x) & (uint16_t)0xff00U) >> 8))) -#define PP_NTOHS(x) PP_HTONS(x) -#define PP_HTONL(x) ((((x) & (uint32_t)0x000000ffUL) << 24) | \ - (((x) & (uint32_t)0x0000ff00UL) << 8) | \ - (((x) & (uint32_t)0x00ff0000UL) >> 8) | \ - (((x) & (uint32_t)0xff000000UL) >> 24)) -#define PP_NTOHL(x) PP_HTONL(x) -#endif /* BYTE_ORDER == BIG_ENDIAN */ - -/* Functions that are not available as standard implementations. - * In cc.h, you can #define these to implementations available on - * your platform to save some code bytes if you use these functions - * in your application, too. - */ - -#ifndef lwip_itoa -/* This can be #defined to itoa() or snprintf(result, bufsize, "%d", number) depending on your platform */ -void lwip_itoa(char *result, size_t bufsize, int number); -#endif -#ifndef lwip_strnicmp -/* This can be #defined to strnicmp() or strncasecmp() depending on your platform */ -int lwip_strnicmp(const char *str1, const char *str2, size_t len); -#endif -#ifndef lwip_stricmp -/* This can be #defined to stricmp() or strcasecmp() depending on your platform */ -int lwip_stricmp(const char *str1, const char *str2); -#endif -#ifndef lwip_strnstr -/* This can be #defined to strnstr() depending on your platform */ -char *lwip_strnstr(const char *buffer, const char *token, size_t n); -#endif -#ifndef lwip_strnistr -/* This can be #defined to strnistr() depending on your platform */ -char *lwip_strnistr(const char *buffer, const char *token, size_t n); -#endif - -#ifdef __cplusplus -} -#endif - -#endif /* LWIP_HDR_DEF_H */ diff --git a/software/script/chameleon_cli_unit.py b/software/script/chameleon_cli_unit.py index 440e22f9..ce031bae 100644 --- a/software/script/chameleon_cli_unit.py +++ b/software/script/chameleon_cli_unit.py @@ -12,17 +12,37 @@ import chameleon_com import chameleon_cmd -import chameleon_cstruct import chameleon_status from chameleon_utils import ArgumentParserNoExit, ArgsParserError, UnexpectedResponseError from chameleon_utils import CLITree +# Colorama shorthands +CR = colorama.Fore.RED +CG = colorama.Fore.GREEN +CC = colorama.Fore.CYAN +CY = colorama.Fore.YELLOW +C0 = colorama.Style.RESET_ALL + +# NXP IDs based on https://www.nxp.com/docs/en/application-note/AN10833.pdf +type_id_SAK_dict = {0x00: "MIFARE Ultralight Classic/C/EV1/Nano | NTAG 2xx", + 0x08: "MIFARE Classic 1K | Plus SE 1K | Plug S 2K | Plus X 2K", + 0x09: "MIFARE Mini 0.3k", + 0x10: "MIFARE Plus 2K", + 0x11: "MIFARE Plus 4K", + 0x18: "MIFARE Classic 4K | Plus S 4K | Plus X 4K", + 0x19: "MIFARE Classic 2K", + 0x20: "MIFARE Plus EV1/EV2 | DESFire EV1/EV2/EV3 | DESFire Light | NTAG 4xx | MIFARE Plus S 2/4K | MIFARE Plus X 2/4K | MIFARE Plus SE 1K", + 0x28: "SmartMX with MIFARE Classic 1K", + 0x38: "SmartMX with MIFARE Classic 4K", + } + class BaseCLIUnit: def __init__(self): # new a device command transfer and receiver instance(Send cmd and receive response) self._device_com: chameleon_com.ChameleonCom | None = None + self._device_cmd: chameleon_cmd.ChameleonCMD = chameleon_cmd.ChameleonCMD(self._device_com) @property def device_com(self) -> chameleon_com.ChameleonCom: @@ -31,10 +51,11 @@ def device_com(self) -> chameleon_com.ChameleonCom: @device_com.setter def device_com(self, com): self._device_com = com + self._device_cmd = chameleon_cmd.ChameleonCMD(self._device_com) @property def cmd(self) -> chameleon_cmd.ChameleonCMD: - return chameleon_cmd.ChameleonCMD(self.device_com) + return self._device_cmd def args_parser(self) -> ArgumentParserNoExit or None: """ @@ -139,7 +160,7 @@ def args_parser(self) -> ArgumentParserNoExit or None: def before_exec(self, args: argparse.Namespace): if super(ReaderRequiredUnit, self).before_exec(args): - ret = self.cmd.is_reader_device_mode() + ret = self.cmd.is_device_reader_mode() if ret: return True else: @@ -219,9 +240,14 @@ def on_exec(self, args: argparse.Namespace): print("Chameleon not found, please connect the device or try connecting manually with the -p flag.") return self.device_com.open(args.port) - print(" { Chameleon connected } ") + self.device_com.commands = self.cmd.get_device_capabilities() + major, minor = self.cmd.get_app_version() + model = 'Ultra' if self.cmd.get_device_model() else 'Lite' + print(f" {{ Chameleon {model} connected: v{major}.{minor} }}") + except Exception as e: - print(f"Chameleon Connect fail: {str(e)}") + print(f"{CR}Chameleon Connect fail: {str(e)}{C0}") + self.device_com.close() @hw_mode.command('set', 'Change device mode to tag reader or tag emulator') @@ -235,10 +261,10 @@ def args_parser(self) -> ArgumentParserNoExit or None: def on_exec(self, args: argparse.Namespace): if args.mode == 'reader' or args.mode == 'r': - self.cmd.set_reader_device_mode(True) + self.cmd.set_device_reader_mode(True) print("Switch to { Tag Reader } mode successfully.") else: - self.cmd.set_reader_device_mode(False) + self.cmd.set_device_reader_mode(False) print("Switch to { Tag Emulator } mode successfully.") @@ -248,7 +274,7 @@ def args_parser(self) -> ArgumentParserNoExit or None: pass def on_exec(self, args: argparse.Namespace): - print(f"- Device Mode ( Tag {'Reader' if self.cmd.is_reader_device_mode() else 'Emulator'} )") + print(f"- Device Mode ( Tag {'Reader' if self.cmd.is_device_reader_mode() else 'Emulator'} )") @hw_chipid.command('get', 'Get device chipset ID') @@ -275,10 +301,11 @@ def args_parser(self) -> ArgumentParserNoExit or None: return None def on_exec(self, args: argparse.Namespace): - fw_version_int = self.cmd.get_firmware_version() - fw_version = f'v{fw_version_int // 256}.{fw_version_int % 256}' + fw_version_tuple = self.cmd.get_app_version() + fw_version = f'v{fw_version_tuple[0]}.{fw_version_tuple[1]}' git_version = self.cmd.get_git_version() - print(f' - Version: {fw_version} ({git_version})') + model = 'Ultra' if self.cmd.get_device_model() else 'Lite' + print(f' - Chameleon {model}, Version: {fw_version} ({git_version})') @hf_14a.command('scan', 'Scan 14a tag, and print basic information') @@ -286,21 +313,43 @@ class HF14AScan(ReaderRequiredUnit): def args_parser(self) -> ArgumentParserNoExit or None: pass - def scan(self): - resp: chameleon_com.Response = self.cmd.scan_tag_14a() - if resp.status == chameleon_status.Device.HF_TAG_OK: - info = chameleon_cstruct.parse_14a_scan_tag_result(resp.data) - print(f"- UID Size: {info['uid_size']}") - print(f"- UID Hex : {info['uid_hex'].upper()}") - print(f"- SAK Hex : {info['sak_hex'].upper()}") - print(f"- ATQA Hex : {info['atqa_hex'].upper()}") - return True + def check_mf1_nt(self): + # detect mf1 support + if self.cmd.mf1_detect_support(): + # detect prng + print("- Mifare Classic technology") + prng_type = self.cmd.mf1_detect_prng() + print(f" # Prng: {chameleon_cmd.MifareClassicPrngType(prng_type)}") + + def sak_info(self, data_tag): + # detect the technology in use based on SAK + int_sak = data_tag['sak'][0] + if int_sak in type_id_SAK_dict: + print(f"- Guessed type(s) from SAK: {type_id_SAK_dict[int_sak]}") + + def scan(self, deep=False): + resp = self.cmd.hf14a_scan() + if resp is not None: + for data_tag in resp: + print(f"- UID : {data_tag['uid'].hex().upper()}") + print(f"- ATQA : {data_tag['atqa'].hex().upper()}") + print(f"- SAK : {data_tag['sak'].hex().upper()}") + if len(data_tag['ats']) > 0: + print(data_tag['ats']) + print(f"- ATS : {data_tag['ats'].hex().upper()}") + if deep: + self.sak_info(data_tag) + # TODO: following checks cannot be done yet if multiple cards are present + if len(resp) == 1: + self.check_mf1_nt() + # TODO: check for ATS support on 14A3 tags + else: + print("Multiple tags detected, skipping deep tests...") else: print("ISO14443-A Tag no found") - return False def on_exec(self, args: argparse.Namespace): - return self.scan() + self.scan() @hf_14a.command('info', 'Scan 14a tag, and print detail information') @@ -308,29 +357,10 @@ class HF14AInfo(ReaderRequiredUnit): def args_parser(self) -> ArgumentParserNoExit or None: pass - def info(self): - # detect mf1 support - resp = self.cmd.detect_mf1_support() - if resp.status == chameleon_status.Device.HF_TAG_OK: - # detect prng - print("- Mifare Classic technology") - resp = self.cmd.detect_mf1_nt_level() - if resp.status == 0x00: - prng_level = "Weak" - elif resp.status == 0x24: - prng_level = "Static" - elif resp.status == 0x25: - prng_level = "Hard" - else: - prng_level = "Unknown" - print(f" # Prng attack: {prng_level}") - def on_exec(self, args: argparse.Namespace): - # reused scan = HF14AScan() scan.device_com = self.device_com - if scan.scan(): - self.info() + scan.scan(deep=1) @hf_mf.command('nested', 'Mifare Classic nested recover key') @@ -363,11 +393,8 @@ def recover_a_key(self, block_known, type_known, key_known, block_target, type_t :return: """ # acquire - dist_resp = self.cmd.detect_nt_distance(block_known, type_known, key_known) - nt_resp = self.cmd.acquire_nested(block_known, type_known, key_known, block_target, type_target) - # parse - dist_obj = chameleon_cstruct.parse_nt_distance_detect_result(dist_resp.data) - nt_obj = chameleon_cstruct.parse_nested_nt_acquire_group(nt_resp.data) + dist_obj = self.cmd.mf1_detect_nt_dist(block_known, type_known, key_known) + nt_obj = self.cmd.mf1_nested_acquire(block_known, type_known, key_known, block_target, type_target) # create cmd cmd_param = f"{dist_obj['uid']} {dist_obj['dist']}" for nt_item in nt_obj: @@ -376,13 +403,15 @@ def recover_a_key(self, block_known, type_known, key_known, block_target, type_t cmd_recover = f"nested.exe {cmd_param}" else: cmd_recover = f"./nested {cmd_param}" + print(f" Executing {cmd_recover}") # start a decrypt process process = self.sub_process(cmd_recover) # wait end while process.is_running(): - msg = f" [ Time elapsed {process.get_time_distance()}ms ]\r" + msg = f" [ Time elapsed {process.get_time_distance()/1000:#.1f}s ]\r" print(msg, end="") + time.sleep(0.1) # clear \r print() @@ -395,11 +424,10 @@ def recover_a_key(self, block_known, type_known, key_known, block_target, type_t key_list.append(sea_obj[1]) # Here you have to verify the password first, and then get the one that is successfully verified # If there is no verified password, it means that the recovery failed, you can try again - print(f" - [{len(key_list)} candidate keys found ]") + print(f" - [{len(key_list)} candidate key(s) found ]") for key in key_list: key_bytes = bytearray.fromhex(key) - ret = self.cmd.auth_mf1_key(block_target, type_target, key_bytes) - if ret.status == chameleon_status.Device.HF_TAG_OK: + if self.cmd.mf1_auth_one_key_block(block_target, type_target, key_bytes): return key else: # No keys recover, and no errors. @@ -422,7 +450,7 @@ def on_exec(self, args: argparse.Namespace): type_target = args.type_target if block_target is not None and type_target is not None: type_target = 0x60 if type_target == 'A' or type_target == 'a' else 0x61 - print(f" - {colorama.Fore.CYAN}Nested recover one key running...{colorama.Style.RESET_ALL}") + print(f" - {C0}Nested recover one key running...{C0}") key = self.recover_a_key(block_known, type_known, key_known, block_target, type_target) if key is None: print("No keys found, you can retry recover.") @@ -456,9 +484,12 @@ def recover_key(self, block_target, type_target): first_recover = True retry_count = 0 while retry_count < 0xFF: - darkside_resp = self.cmd.acquire_darkside(block_target, type_target, first_recover, 15) + darkside_resp = self.cmd.mf1_darkside_acquire(block_target, type_target, first_recover, 15) first_recover = False # not first run. - darkside_obj = chameleon_cstruct.parse_darkside_acquire_result(darkside_resp.data) + if darkside_resp[0] != chameleon_cmd.MifareClassicDarksideStatus.OK: + print(f"Darkside error: {chameleon_cmd.MifareClassicDarksideStatus(darkside_resp[0])}") + break + darkside_obj = darkside_resp[1] self.darkside_list.append(darkside_obj) recover_params = f"{darkside_obj['uid']}" for darkside_item in self.darkside_list: @@ -489,7 +520,7 @@ def recover_key(self, block_target, type_target): # auth key for key in key_list: key_bytes = bytearray.fromhex(key) - auth_ret = self.cmd.auth_mf1_key(block_target, type_target, key_bytes) + auth_ret = self.cmd.mf1_auth_one_key_block(block_target, type_target, key_bytes) if auth_ret.status == chameleon_status.Device.HF_TAG_OK: return key return None @@ -535,8 +566,8 @@ class HFMFRDBL(BaseMF1AuthOpera): # hf mf rdbl -b 2 -t A -k FFFFFFFFFFFF def on_exec(self, args: argparse.Namespace): param = self.get_param(args) - resp = self.cmd.read_mf1_block(param.block, param.type, param.key) - print(f" - Data: {resp.data.hex()}") + resp = self.cmd.mf1_read_one_block(param.block, param.type, param.key) + print(f" - Data: {resp.hex()}") @hf_mf.command('wrbl', 'Mifare Classic write one block') @@ -553,11 +584,11 @@ def on_exec(self, args: argparse.Namespace): if not re.match(r"^[a-fA-F0-9]{32}$", args.data): raise ArgsParserError("Data must include 32 HEX symbols") param.data = bytearray.fromhex(args.data) - resp = self.cmd.write_mf1_block(param.block, param.type, param.key, param.data) - if resp.status == chameleon_status.Device.HF_TAG_OK: - print(f" - {colorama.Fore.GREEN}Write done.{colorama.Style.RESET_ALL}") + resp = self.cmd.mf1_write_one_block(param.block, param.type, param.key, param.data) + if resp: + print(f" - {CG}Write done.{C0}") else: - print(f" - {colorama.Fore.RED}Write fail.{colorama.Style.RESET_ALL}") + print(f" - {CR}Write fail.{C0}") @hf_mf_detection.command('enable', 'Detection enable') @@ -570,7 +601,7 @@ def args_parser(self) -> ArgumentParserNoExit or None: # hf mf detection enable -e 1 def on_exec(self, args: argparse.Namespace): enable = True if args.enable == 1 else False - self.cmd.set_mf1_detection_enable(enable) + self.cmd.mf1_set_detection_enable(enable) print(f" - Set mf1 detection {'enable' if enable else 'disable'}.") @@ -581,8 +612,7 @@ def args_parser(self) -> ArgumentParserNoExit or None: # hf mf detection count def on_exec(self, args: argparse.Namespace): - data_bytes = self.cmd.get_mf1_detection_count().data - count = int.from_bytes(data_bytes, "little", signed=False) + count = self.cmd.mf1_get_detection_count() print(f" - MF1 detection log count = {count}") @@ -599,11 +629,17 @@ def decrypt_by_list(self, rs: list): :param rs: :return: """ - keys = [] + msg1 = f" > {len(rs)} records => " + msg2 = f"/{(len(rs)*(len(rs)-1))//2} combinations. " + msg3 = f" key(s) found" + n = 1 + keys = set() for i in range(len(rs)): item0 = rs[i] for j in range(i + 1, len(rs)): item1 = rs[j] + # TODO: if some keys already recovered, test them on item before running mfkey32 on item + # TODO: if some keys already recovered, remove corresponding items cmd_base = f"{item0['uid']} {item0['nt']} {item0['nr']} {item0['ar']}" cmd_base += f" {item1['nt']} {item1['nr']} {item1['ar']}" if sys.platform == "win32": @@ -621,29 +657,44 @@ def decrypt_by_list(self, rs: list): # print(output_str) sea_obj = re.search(r"([a-fA-F0-9]{12})", output_str, flags=re.MULTILINE) if sea_obj is not None: - keys.append(sea_obj[1]) - + keys.add(sea_obj[1]) + print(f"{msg1}{n}{msg2}{len(keys)}{msg3}\r", end="") + n += 1 + print() return keys # hf mf detection decrypt def on_exec(self, args: argparse.Namespace): - buffer = bytearray() index = 0 - count = int.from_bytes(self.cmd.get_mf1_detection_count().data, "little", signed=False) + count = self.cmd.mf1_get_detection_count() if count == 0: print(" - No detection log to download") return print(f" - MF1 detection log count = {count}, start download", end="") + result_list = [] while index < count: - tmp = self.cmd.get_mf1_detection_log(index).data - recv_count = int(len(tmp) / HFMFDetectionDecrypt.detection_log_size) + tmp = self.cmd.mf1_get_detection_log(index) + recv_count = len(tmp) index += recv_count - buffer.extend(tmp) - print(".", end="") + result_list.extend(tmp) + print("."*recv_count, end="") print() - print(f" - Download done ({len(buffer)}bytes), start parse and decrypt") + print(f" - Download done ({len(result_list)} records), start parse and decrypt") + # classify + result_maps = {} + for item in result_list: + uid = item['uid'] + if uid not in result_maps: + result_maps[uid] = {} + block = item['block'] + if block not in result_maps[uid]: + result_maps[uid][block] = {} + type = item['type'] + if type not in result_maps[uid][block]: + result_maps[uid][block][type] = [] + + result_maps[uid][block][type].append(item) - result_maps = chameleon_cstruct.parse_mf1_detection_result(buffer) for uid in result_maps.keys(): print(f" - Detection log for uid [{uid.upper()}]") result_maps_for_uid = result_maps[uid] @@ -654,11 +705,15 @@ def on_exec(self, args: argparse.Namespace): records = result_maps_for_uid[block]['A'] if len(records) > 1: result_maps[uid][block]['A'] = self.decrypt_by_list(records) + else: + print(f" > {len(records)} record") if 'B' in result_maps_for_uid[block]: # print(f" - B record: { result_maps[block]['B'] }") records = result_maps_for_uid[block]['B'] if len(records) > 1: result_maps[uid][block]['B'] = self.decrypt_by_list(records) + else: + print(f" > {len(records)} record") print(" > Result ---------------------------") for block in result_maps_for_uid.keys(): if 'A' in result_maps_for_uid[block]: @@ -704,14 +759,16 @@ def on_exec(self, args: argparse.Namespace): index = 0 block = 0 - while index < len(buffer): + max_blocks = (self.device_com.data_max_length - 1) // 16 + while index + 16 < len(buffer): # split a block from buffer - block_data = buffer[index: index + 16] - index += 16 + block_data = buffer[index: index + 16*max_blocks] + n_blocks = len(block_data) // 16 + index += 16*n_blocks # load to device - self.cmd.set_mf1_block_data(block, block_data) - print('.', end='') - block += 1 + self.cmd.mf1_write_emu_block_data(block, block_data) + print('.'*n_blocks, end='') + block += n_blocks print("\n - Load success") @@ -735,9 +792,9 @@ def on_exec(self, args: argparse.Namespace): else: content_type = args.type - selected_slot = self.cmd.get_active_slot().data[0] - slot_info = self.cmd.get_slot_info().data - tag_type = chameleon_cmd.TagSpecificType(slot_info[selected_slot * 2]) + selected_slot = self.cmd.get_active_slot() + slot_info = self.cmd.get_slot_info() + tag_type = chameleon_cmd.TagSpecificType(slot_info[selected_slot][0]) if tag_type == chameleon_cmd.TagSpecificType.TAG_TYPE_MIFARE_Mini: block_count = 20 elif tag_type == chameleon_cmd.TagSpecificType.TAG_TYPE_MIFARE_1024: @@ -749,19 +806,22 @@ def on_exec(self, args: argparse.Namespace): else: raise Exception("Card in current slot is not Mifare Classic/Plus in SL1 mode") - with open(file, 'wb') as fd: - block = 0 - while block < block_count: - response = self.cmd.get_mf1_block_data(block, 1) - print('.', end='') - block += 1 - if content_type == 'hex': - hex_char_repr = binascii.hexlify(response.data) - fd.write(hex_char_repr) - fd.write(bytes([0x0a])) - else: - fd.write(response.data) + index = 0 + data = bytearray(0) + max_blocks = self.device_com.data_max_length // 16 + while block_count > 0: + chunk_count = min(block_count, max_blocks) + data.extend(self.cmd.mf1_read_emu_block_data(index, chunk_count)) + index += chunk_count + block_count -= chunk_count + print('.'*chunk_count, end='') + with open(file, 'wb') as fd: + if content_type == 'hex': + for i in range(len(data) // 16): + fd.write(binascii.hexlify(data[i*16:(i+1)*16])+b'\n') + else: + fd.write(data) print("\n - Read success") @@ -789,16 +849,16 @@ def args_parser(self) -> ArgumentParserNoExit or None: # hf mf settings def on_exec(self, args: argparse.Namespace): if args.gen1a != -1: - self.cmd.set_mf1_gen1a_mode(args.gen1a) + self.cmd.mf1_set_gen1a_mode(args.gen1a) print(f' - Set gen1a mode to {"enabled" if args.gen1a else "disabled"} success') if args.gen2 != -1: - self.cmd.set_mf1_gen2_mode(args.gen2) + self.cmd.mf1_set_gen2_mode(args.gen2) print(f' - Set gen2 mode to {"enabled" if args.gen2 else "disabled"} success') if args.coll != -1: - self.cmd.set_mf1_block_anti_coll_mode(args.coll) + self.cmd.mf1_set_block_anti_coll_mode(args.coll) print(f' - Set anti-collision mode to {"enabled" if args.coll else "disabled"} success') if args.write != -1: - self.cmd.set_mf1_write_mode(args.write) + self.cmd.mf1_set_write_mode(args.write) print(f' - Set write mode to {chameleon_cmd.MifareClassicWriteMode(args.write)} success') print(' - Emulator settings updated') @@ -807,36 +867,44 @@ def on_exec(self, args: argparse.Namespace): class HFMFSim(DeviceRequiredUnit): def args_parser(self) -> ArgumentParserNoExit or None: parser = ArgumentParserNoExit() - parser.add_argument('--sak', type=str, required=True, help="Select AcKnowledge(hex)", metavar="hex") - parser.add_argument('--atqa', type=str, required=True, help="Answer To Request(hex)", metavar="hex") parser.add_argument('--uid', type=str, required=True, help="Unique ID(hex)", metavar="hex") + parser.add_argument('--atqa', type=str, required=True, help="Answer To Request(hex)", metavar="hex") + parser.add_argument('--sak', type=str, required=True, help="Select AcKnowledge(hex)", metavar="hex") + parser.add_argument('--ats', type=str, required=False, help="Answer To Select(hex)", metavar="hex") return parser # hf mf sim --sak 08 --atqa 0400 --uid DEADBEEF def on_exec(self, args: argparse.Namespace): - sak_str: str = args.sak.strip() - atqa_str: str = args.atqa.strip() uid_str: str = args.uid.strip() - - if re.match(r"[a-fA-F0-9]{2}", sak_str) is not None: - sak = bytearray.fromhex(sak_str) + if re.match(r"[a-fA-F0-9]+", uid_str) is not None: + uid = bytes.fromhex(uid_str) + if len(uid) not in [4, 7, 10]: + raise Exception("UID length error") else: - raise Exception("SAK must be hex(2byte)") + raise Exception("UID must be hex") + atqa_str: str = args.atqa.strip() if re.match(r"[a-fA-F0-9]{4}", atqa_str) is not None: - atqa = bytearray.fromhex(atqa_str) + atqa = bytes.fromhex(atqa_str) else: raise Exception("ATQA must be hex(4byte)") - if re.match(r"[a-fA-F0-9]+", uid_str) is not None: - uid_len = len(uid_str) - if uid_len != 8 and uid_len != 14 and uid_len != 20: - raise Exception("UID length error") - uid = bytearray.fromhex(uid_str) + sak_str: str = args.sak.strip() + if re.match(r"[a-fA-F0-9]{2}", sak_str) is not None: + sak = bytes.fromhex(sak_str) else: - raise Exception("UID must be hex") + raise Exception("SAK must be hex(2byte)") - self.cmd.set_mf1_anti_collision_res(sak, atqa, uid) + if args.ats is not None: + ats_str: str = args.ats.strip() + if re.match(r"[a-fA-F0-9]+", ats_str) is not None: + ats = bytes.fromhex(ats_str) + else: + raise Exception("ATS must be hex") + else: + ats = b'' + + self.cmd.hf14a_set_anti_coll_data(uid, atqa, sak, ats) print(" - Set anti-collision resources success") @@ -846,17 +914,12 @@ def args_parser(self) -> ArgumentParserNoExit or None: pass def scan(self): - resp: chameleon_com.Response = self.cmd.get_mf1_anti_coll_data() - if resp.status == chameleon_status.Device.STATUS_DEVICE_SUCCESS: - info = chameleon_cstruct.parse_14a_scan_tag_result(resp.data) - print(f"- UID Size: {info['uid_size']}") - print(f"- UID Hex : {info['uid_hex'].upper()}") - print(f"- SAK Hex : {info['sak_hex'].upper()}") - print(f"- ATQA Hex : {info['atqa_hex'].upper()}") - return True - else: - print("No data loaded in slot") - return False + resp = self.cmd.hf14a_get_anti_coll_data() + print(f"- UID : {resp['uid'].hex().upper()}") + print(f"- ATQA : {resp['atqa'].hex().upper()}") + print(f"- SAK : {resp['sak'].hex().upper()}") + if len(resp['ats']) > 0: + print(f"- ATS : {resp['ats'].hex().upper()}") def on_exec(self, args: argparse.Namespace): return self.scan() @@ -868,9 +931,8 @@ def args_parser(self) -> ArgumentParserNoExit or None: return None def on_exec(self, args: argparse.Namespace): - resp = self.cmd.read_em_410x() - id_hex = resp.data.hex() - print(f" - EM410x ID(10H): {colorama.Fore.GREEN}{id_hex}{colorama.Style.RESET_ALL}") + id = self.cmd.em410x_scan() + print(f" - EM410x ID(10H): {CG}{id.hex()}{C0}") class LFEMCardRequiredUnit(DeviceRequiredUnit): @@ -907,8 +969,8 @@ def before_exec(self, args: argparse.Namespace): # lf em write --id 4400999559 def on_exec(self, args: argparse.Namespace): id_hex = args.id - id_bytes = bytearray.fromhex(id_hex) - self.cmd.write_em_410x_to_t55xx(id_bytes) + id_bytes = bytes.fromhex(id_hex) + self.cmd.em410x_write_to_t55xx(id_bytes) print(f" - EM410x ID(10H): {id_hex} write done.") @@ -956,40 +1018,62 @@ class HWSlotList(DeviceRequiredUnit): def args_parser(self) -> ArgumentParserNoExit or None: parser = ArgumentParserNoExit() parser.add_argument('-e', '--extend', type=int, required=False, - help="Show slot nicknames and Mifare Classic emulator settings. 0 - skip, 1 - show (" - "default, 2 - show emulator settings for each slot)", choices=[0, 1, 2], default=1) + help="Show slot nicknames and Mifare Classic emulator settings. 0 - skip, 1 - show (default)", choices=[0, 1], default=1) return parser def get_slot_name(self, slot, sense): try: - return self.cmd.get_slot_tag_nick_name(slot, sense).data.decode() + name = self.cmd.get_slot_tag_nick(slot, sense).decode(encoding="utf8") + return len(name),len(CC+C0),f'{CC}{name}{C0}' except UnexpectedResponseError: - return "Empty" + return 0,0,'' except UnicodeDecodeError: - return "Non UTF-8" + name = "UTF8 Err" + return len(name),len(CR+C0),f'{CR}{name}{C0}' # hw slot list def on_exec(self, args: argparse.Namespace): - data = self.cmd.get_slot_info().data - selected = chameleon_cmd.SlotNumber.from_fw(self.cmd.get_active_slot().data[0]) - enabled = self.cmd.get_enabled_slots().data + slotinfo = self.cmd.get_slot_info() + selected = chameleon_cmd.SlotNumber.from_fw(self.cmd.get_active_slot()) + enabled = self.cmd.get_enabled_slots() + maxnamelength = 0 + if args.extend: + slotnames = [] + for slot in chameleon_cmd.SlotNumber: + hfn = self.get_slot_name(slot, chameleon_cmd.TagSenseType.TAG_SENSE_HF) + lfn = self.get_slot_name(slot, chameleon_cmd.TagSenseType.TAG_SENSE_LF) + m = max(hfn[0], lfn[0]) + maxnamelength = m if m > maxnamelength else maxnamelength + slotnames.append((hfn, lfn)) for slot in chameleon_cmd.SlotNumber: - print(f' - Slot {slot} data{" (active)" if slot == selected else ""}' - f'{" (disabled)" if not enabled[chameleon_cmd.SlotNumber.to_fw(slot)] else ""}:') + fwslot = chameleon_cmd.SlotNumber.to_fw(slot) + hf_tag_type = chameleon_cmd.TagSpecificType(slotinfo[fwslot][0]) + lf_tag_type = chameleon_cmd.TagSpecificType(slotinfo[fwslot][1]) + print(f' - {f"Slot {slot}:":{4+maxnamelength+1}}' + f'{f"({CG}active{C0})" if slot == selected else ""}' + f'{f"({CR}disabled{C0}) " if not enabled[fwslot] else ""}') print(f' HF: ' - f'{(self.get_slot_name(slot, chameleon_cmd.TagSenseType.TAG_SENSE_HF) + " - ") if args.extend else ""}' - f'{chameleon_cmd.TagSpecificType(data[chameleon_cmd.SlotNumber.to_fw(slot) * 2])}') + f'{(slotnames[fwslot][0][2] if args.extend else ""):{maxnamelength+slotnames[fwslot][0][1]+1 if args.extend else maxnamelength+1}}' + f'{f"{CY if enabled[fwslot] else C0}{hf_tag_type}{C0}" if hf_tag_type != chameleon_cmd.TagSpecificType.TAG_TYPE_UNKNOWN else "undef"}') + if args.extend == 1 and \ + enabled[fwslot] and \ + slot == selected and \ + hf_tag_type in [ + chameleon_cmd.TagSpecificType.TAG_TYPE_MIFARE_Mini, + chameleon_cmd.TagSpecificType.TAG_TYPE_MIFARE_1024, + chameleon_cmd.TagSpecificType.TAG_TYPE_MIFARE_2048, + chameleon_cmd.TagSpecificType.TAG_TYPE_MIFARE_4096, + ]: + config = self.cmd.mf1_get_emulator_config() + print(' - Mifare Classic emulator settings:') + print(f' {"Detection (mfkey32) mode:":40}{f"{CG}enabled{C0}" if config["detection"] else f"{CR}disabled{C0}"}') + print(f' {"Gen1A magic mode:":40}{f"{CG}enabled{C0}" if config["gen1a_mode"] else f"{CR}disabled{C0}"}') + print(f' {"Gen2 magic mode:":40}{f"{CG}enabled{C0}" if config["gen2_mode"] else f"{CR}disabled{C0}"}') + print(f' {"Use anti-collision data from block 0:":40}{f"{CG}enabled{C0}" if config["block_anti_coll_mode"] else f"{CR}disabled{C0}"}') + print(f' {"Write mode:":40}{CY}{chameleon_cmd.MifareClassicWriteMode(config["write_mode"])}{C0}') print(f' LF: ' - f'{(self.get_slot_name(slot, chameleon_cmd.TagSenseType.TAG_SENSE_LF) + " - ") if args.extend else ""}' - f'{chameleon_cmd.TagSpecificType(data[chameleon_cmd.SlotNumber.to_fw(slot) * 2 + 1])}') - if args.extend == 2 or args.extend == 1 and enabled[chameleon_cmd.SlotNumber.to_fw(slot)]: - config = self.cmd.get_mf1_emulator_settings().data - print(' - Mifare Classic emulator settings:') - print(f' Detection (mfkey32) mode: {"enabled" if config[0] else "disabled"}') - print(f' Gen1A magic mode: {"enabled" if config[1] else "disabled"}') - print(f' Gen2 magic mode: {"enabled" if config[2] else "disabled"}') - print(f' Use anti-collision data from block 0: {"enabled" if config[3] else "disabled"}') - print(f' Write mode: {chameleon_cmd.MifareClassicWriteMode(config[4])}') + f'{(slotnames[fwslot][1][2] if args.extend else ""):{maxnamelength+slotnames[fwslot][1][1]+1 if args.extend else maxnamelength+1}}' + f'{f"{CY if enabled[fwslot] else C0}{lf_tag_type}{C0}" if lf_tag_type != chameleon_cmd.TagSpecificType.TAG_TYPE_UNKNOWN else "undef"}') @hw_slot.command('change', 'Set emulation tag slot activated.') @@ -1001,7 +1085,7 @@ def args_parser(self) -> ArgumentParserNoExit or None: # hw slot change -s 1 def on_exec(self, args: argparse.Namespace): slot_index = args.slot - self.cmd.set_slot_activated(slot_index) + self.cmd.set_active_slot(slot_index) print(f" - Set slot {slot_index} activated success.") @@ -1099,8 +1183,7 @@ def args_parser(self) -> ArgumentParserNoExit or None: # lf em sim set --id 4545454545 def on_exec(self, args: argparse.Namespace): id_hex = args.id - id_bytes = bytearray.fromhex(id_hex) - self.cmd.set_em410x_sim_id(id_bytes) + self.cmd.em410x_set_emu_id(bytes.fromhex(id_hex)) print(' - Set em410x tag id success.') @@ -1111,9 +1194,9 @@ def args_parser(self) -> ArgumentParserNoExit or None: # lf em sim get def on_exec(self, args: argparse.Namespace): - response = self.cmd.get_em410x_sim_id() + response = self.cmd.em410x_get_emu_id() print(' - Get em410x tag id success.') - print(f'ID: {response.data.hex()}') + print(f'ID: {response.hex()}') @hw_slot_nick.command('set', 'Set tag nick name for slot') @@ -1133,7 +1216,7 @@ def on_exec(self, args: argparse.Namespace): encoded_name = name.encode(encoding="utf8") if len(encoded_name) > 32: raise ValueError("Your tag nick name too long.") - self.cmd.set_slot_tag_nick_name(slot_num, sense_type, encoded_name) + self.cmd.set_slot_tag_nick(slot_num, sense_type, encoded_name) print(f' - Set tag nick name for slot {slot_num} success.') @@ -1149,8 +1232,8 @@ def args_parser(self) -> ArgumentParserNoExit or None: def on_exec(self, args: argparse.Namespace): slot_num = args.slot sense_type = args.sense_type - res = self.cmd.get_slot_tag_nick_name(slot_num, sense_type) - print(f' - Get tag nick name for slot {slot_num}: {res.data.decode()}') + res = self.cmd.get_slot_tag_nick(slot_num, sense_type) + print(f' - Get tag nick name for slot {slot_num}: {res.decode(encoding="utf8")}') @hw_slot.command('update', 'Update config & data to device flash') @@ -1160,7 +1243,7 @@ def args_parser(self) -> ArgumentParserNoExit or None: # hw slot update def on_exec(self, args: argparse.Namespace): - self.cmd.update_slot_data_config() + self.cmd.slot_data_config_save() print(' - Update config and data from device memory to flash success.') @@ -1189,7 +1272,7 @@ def on_exec(self, args: argparse.Namespace): print(f' Slot {slot} setting done.') # update config and save to flash - self.cmd.update_slot_data_config() + self.cmd.slot_data_config_save() print(' - Succeeded opening all slots and setting data to default.') @@ -1201,7 +1284,7 @@ def args_parser(self) -> ArgumentParserNoExit or None: # hw dfu def on_exec(self, args: argparse.Namespace): print("Application restarting...") - self.cmd.enter_dfu_mode() + self.cmd.enter_bootloader() # In theory, after the above command is executed, the dfu mode will enter, and then the USB will restart, # To judge whether to enter the USB successfully, we only need to judge whether the USB becomes the VID and PID # of the DFU device. @@ -1218,12 +1301,12 @@ def args_parser(self) -> ArgumentParserNoExit or None: return None def on_exec(self, args: argparse.Namespace): - resp: chameleon_com.Response = self.cmd.get_settings_animation() - if resp.data[0] == 0: + resp = self.cmd.get_animation_mode() + if resp == 0: print("Full animation") - elif resp.data[0] == 1: + elif resp == 1: print("Minimal animation") - elif resp.data[0] == 2: + elif resp == 2: print("No animation") else: print("Unknown setting value, something failed.") @@ -1240,7 +1323,7 @@ def args_parser(self) -> ArgumentParserNoExit or None: def on_exec(self, args: argparse.Namespace): mode = args.mode - self.cmd.set_settings_animation(mode) + self.cmd.set_animation_mode(mode) print("Animation mode change success. Do not forget to store your settings in flash!") @@ -1251,8 +1334,7 @@ def args_parser(self) -> ArgumentParserNoExit or None: def on_exec(self, args: argparse.Namespace): print("Storing settings...") - resp: chameleon_com.Response = self.cmd.store_settings() - if resp.status == chameleon_status.Device.STATUS_DEVICE_SUCCESS: + if self.cmd.save_settings(): print(" - Store success @.@~") else: print(" - Store failed") @@ -1265,8 +1347,7 @@ def args_parser(self) -> ArgumentParserNoExit or None: def on_exec(self, args: argparse.Namespace): print("Initializing settings...") - resp: chameleon_com.Response = self.cmd.reset_settings() - if resp.status == chameleon_status.Device.STATUS_DEVICE_SUCCESS: + if self.cmd.reset_settings(): print(" - Reset success @.@~") else: print(" - Reset failed") @@ -1286,8 +1367,7 @@ def on_exec(self, args: argparse.Namespace): if not args.i_know_what_im_doing: print("This time your data's safe. Read the command documentation next time.") return - resp = self.cmd.factory_reset() - if resp.status == chameleon_status.Device.STATUS_DEVICE_SUCCESS: + if self.cmd.wipe_fds(): print(" - Reset successful! Please reconnect.") # let time for comm thread to close port time.sleep(0.1) @@ -1304,14 +1384,12 @@ def args_parser(self) -> ArgumentParserNoExit or None: return None def on_exec(self, args: argparse.Namespace): - resp = self.cmd.battery_information() - voltage = int.from_bytes(resp.data[:2], 'big') - percentage = resp.data[2] + voltage, percentage = self.cmd.get_battery_info() print(" - Battery information:") - print(f" voltage -> {voltage}mV") + print(f" voltage -> {voltage} mV") print(f" percentage -> {percentage}%") if percentage < HWBatteryInfo.BATTERY_LOW_LEVEL: - print(f"{colorama.Fore.RED}[!] Low battery, please charge.{colorama.Style.RESET_ALL}") + print(f"{CR}[!] Low battery, please charge.{C0}") @hw_settings_button_press.command('get', 'Get button press function of Button A and Button B.') @@ -1325,14 +1403,14 @@ def on_exec(self, args: argparse.Namespace): button_list = [chameleon_cmd.ButtonType.ButtonA, chameleon_cmd.ButtonType.ButtonB, ] print("") for button in button_list: - resp = self.cmd.get_button_press_fun(button) - resp_long = self.cmd.get_long_button_press_fun(button) - button_fn = chameleon_cmd.ButtonPressFunction.from_int(resp.data[0]) - button_long_fn = chameleon_cmd.ButtonPressFunction.from_int(resp_long.data[0]) - print(f" - {colorama.Fore.GREEN}{button} {colorama.Fore.YELLOW}short{colorama.Style.RESET_ALL}:" + resp = self.cmd.get_button_press_config(button) + resp_long = self.cmd.get_long_button_press_config(button) + button_fn = chameleon_cmd.ButtonPressFunction.from_int(resp) + button_long_fn = chameleon_cmd.ButtonPressFunction.from_int(resp_long) + print(f" - {CG}{button} {CY}short{C0}:" f" {button_fn}") print(f" usage: {button_fn.usage()}") - print(f" - {colorama.Fore.GREEN}{button} {colorama.Fore.YELLOW}long {colorama.Style.RESET_ALL}:" + print(f" - {CG}{button} {CY}long {C0}:" f" {button_long_fn}") print(f" usage: {button_long_fn.usage()}") print("") @@ -1359,9 +1437,9 @@ def on_exec(self, args: argparse.Namespace): button = chameleon_cmd.ButtonType.from_str(args.b) function = chameleon_cmd.ButtonPressFunction.from_int(args.f) if args.long: - self.cmd.set_long_button_press_fun(button, function) + self.cmd.set_long_button_press_config(button, function) else: - self.cmd.set_button_press_fun(button, function) + self.cmd.set_button_press_config(button, function) print(" - Successfully set button function to settings") @@ -1374,23 +1452,23 @@ def args_parser(self) -> ArgumentParserNoExit or None: return parser def on_exec(self, args: argparse.Namespace): - resp = self.cmd.get_ble_connect_key() + resp = self.cmd.get_ble_pairing_key() print(" - The current key of the device(ascii): " - f"{colorama.Fore.GREEN}{resp.data.decode(encoding='ascii')}{colorama.Style.RESET_ALL}") - + f"{CG}{resp.decode(encoding='ascii')}{C0}") + if args.key != None: if len(args.key) != 6: - print(f" - {colorama.Fore.RED}The ble connect key length must be 6{colorama.Style.RESET_ALL}") + print(f" - {CR}The ble connect key length must be 6{C0}") return if re.match(r'[0-9]{6}', args.key): self.cmd.set_ble_connect_key(args.key) print(" - Successfully set ble connect key to :", end='') - print(f"{colorama.Fore.GREEN}" + print(f"{CG}" f" { args.key }" - f"{colorama.Style.RESET_ALL}" + f"{C0}" ) else: - print(f" - {colorama.Fore.RED}Only 6 ASCII characters from 0 to 9 are supported.{colorama.Style.RESET_ALL}") + print(f" - {CR}Only 6 ASCII characters from 0 to 9 are supported.{C0}") @hw_settings.command('blepair', 'Check if BLE pairing is enabled, or set the enable switch for BLE pairing.') @@ -1404,24 +1482,24 @@ def args_parser(self) -> ArgumentParserNoExit or None: def on_exec(self, args: argparse.Namespace): is_pairing_enable = self.cmd.get_ble_pairing_enable() print(f" - Is ble pairing enable: ", end='') - color = colorama.Fore.GREEN if is_pairing_enable else colorama.Fore.RED + color = CG if is_pairing_enable else CR print( f"{color}" f"{ 'Yes' if is_pairing_enable else 'No' }" - f"{colorama.Style.RESET_ALL}" + f"{C0}" ) if args.enable is not None: if args.enable == 1 and is_pairing_enable: - print(f"{colorama.Fore.YELLOW} It is already in an enabled state.{colorama.Style.RESET_ALL}") + print(f"{CY} It is already in an enabled state.{C0}") return if args.enable == 0 and not is_pairing_enable: - print(f"{colorama.Fore.YELLOW} It is already in a non enabled state.{colorama.Style.RESET_ALL}") + print(f"{CY} It is already in a non enabled state.{C0}") return self.cmd.set_ble_pairing_enable(args.enable) print(f" - Successfully change ble pairing to " - f"{colorama.Fore.GREEN if args.enable else colorama.Fore.RED}" + f"{CG if args.enable else CR}" f"{ 'Enable' if args.enable else 'Disable' } " - f"{colorama.Style.RESET_ALL}" + f"{C0}" "state.") @@ -1449,5 +1527,12 @@ def on_exec(self, args: argparse.Namespace): response = self.cmd.device.send_cmd_sync(args.command, data=bytes.fromhex(args.data), status=0x0) print(" - Received:") print(f" Command: {response.cmd}") - print(f" Status: {response.status}") + status_string = f" Status: {response.status:#02x}" + if response.status in chameleon_status.Device: + status_string += f" {chameleon_status.Device[response.status]}" + if response.status in chameleon_status.message: + status_string += f": {chameleon_status.message[response.status]}" + print(status_string) + else: + print(f" Status: {response.status:#02x}") print(f" Data (HEX): {response.data.hex()}") diff --git a/software/script/chameleon_cmd.py b/software/script/chameleon_cmd.py index 2ea7ffd5..100c4c3b 100644 --- a/software/script/chameleon_cmd.py +++ b/software/script/chameleon_cmd.py @@ -8,9 +8,9 @@ CURRENT_VERSION_SETTINGS = 5 DATA_CMD_GET_APP_VERSION = 1000 -DATA_CMD_CHANGE_MODE = 1001 +DATA_CMD_CHANGE_DEVICE_MODE = 1001 DATA_CMD_GET_DEVICE_MODE = 1002 -DATA_CMD_SET_SLOT_ACTIVATED = 1003 +DATA_CMD_SET_ACTIVE_SLOT = 1003 DATA_CMD_SET_SLOT_TAG_TYPE = 1004 DATA_CMD_SET_SLOT_DATA_DEFAULT = 1005 DATA_CMD_SET_SLOT_ENABLE = 1006 @@ -47,53 +47,57 @@ DATA_CMD_GET_LONG_BUTTON_PRESS_CONFIG = 1028 DATA_CMD_SET_LONG_BUTTON_PRESS_CONFIG = 1029 -DATA_CMD_SET_BLE_CONNECT_KEY_CONFIG = 1030 -DATA_CMD_GET_BLE_CONNECT_KEY_CONFIG = 1031 - +DATA_CMD_SET_BLE_PAIRING_KEY = 1030 +DATA_CMD_GET_BLE_PAIRING_KEY = 1031 DATA_CMD_DELETE_ALL_BLE_BONDS = 1032 -DATA_CMD_GET_DEVICE = 1033 -DATA_CMD_GET_SETTINGS = 1034 +DATA_CMD_GET_DEVICE_MODEL = 1033 +# FIXME: implemented but unused in CLI commands +DATA_CMD_GET_DEVICE_SETTINGS = 1034 DATA_CMD_GET_DEVICE_CAPABILITIES = 1035 DATA_CMD_GET_BLE_PAIRING_ENABLE = 1036 DATA_CMD_SET_BLE_PAIRING_ENABLE = 1037 -DATA_CMD_SCAN_14A_TAG = 2000 -DATA_CMD_MF1_SUPPORT_DETECT = 2001 -DATA_CMD_MF1_NT_LEVEL_DETECT = 2002 -DATA_CMD_MF1_DARKSIDE_DETECT = 2003 +DATA_CMD_HF14A_SCAN = 2000 +DATA_CMD_MF1_DETECT_SUPPORT = 2001 +DATA_CMD_MF1_DETECT_PRNG = 2002 +# FIXME: implemented but unused in CLI commands +DATA_CMD_MF1_DETECT_DARKSIDE = 2003 DATA_CMD_MF1_DARKSIDE_ACQUIRE = 2004 -DATA_CMD_MF1_NT_DIST_DETECT = 2005 +DATA_CMD_MF1_DETECT_NT_DIST = 2005 DATA_CMD_MF1_NESTED_ACQUIRE = 2006 -DATA_CMD_MF1_CHECK_ONE_KEY_BLOCK = 2007 +DATA_CMD_MF1_AUTH_ONE_KEY_BLOCK = 2007 DATA_CMD_MF1_READ_ONE_BLOCK = 2008 DATA_CMD_MF1_WRITE_ONE_BLOCK = 2009 -DATA_CMD_SCAN_EM410X_TAG = 3000 -DATA_CMD_WRITE_EM410X_TO_T5577 = 3001 - -DATA_CMD_LOAD_MF1_EMU_BLOCK_DATA = 4000 -DATA_CMD_SET_MF1_ANTI_COLLISION_RES = 4001 - -DATA_CMD_SET_MF1_DETECTION_ENABLE = 4004 -DATA_CMD_GET_MF1_DETECTION_COUNT = 4005 -DATA_CMD_GET_MF1_DETECTION_RESULT = 4006 - -DATA_CMD_READ_MF1_EMU_BLOCK_DATA = 4008 - -DATA_CMD_GET_MF1_EMULATOR_CONFIG = 4009 -DATA_CMD_GET_MF1_GEN1A_MODE = 4010 -DATA_CMD_SET_MF1_GEN1A_MODE = 4011 -DATA_CMD_GET_MF1_GEN2_MODE = 4012 -DATA_CMD_SET_MF1_GEN2_MODE = 4013 -DATA_CMD_GET_MF1_USE_FIRST_BLOCK_COLL = 4014 -DATA_CMD_SET_MF1_USE_FIRST_BLOCK_COLL = 4015 -DATA_CMD_GET_MF1_WRITE_MODE = 4016 -DATA_CMD_SET_MF1_WRITE_MODE = 4017 -DATA_CMD_GET_MF1_ANTI_COLL_DATA = 4018 - -DATA_CMD_SET_EM410X_EMU_ID = 5000 -DATA_CMD_GET_EM410X_EMU_ID = 5001 +DATA_CMD_EM410X_SCAN = 3000 +DATA_CMD_EM410X_WRITE_TO_T55XX = 3001 + +DATA_CMD_MF1_WRITE_EMU_BLOCK_DATA = 4000 +DATA_CMD_HF14A_SET_ANTI_COLL_DATA = 4001 +DATA_CMD_MF1_SET_DETECTION_ENABLE = 4004 +DATA_CMD_MF1_GET_DETECTION_COUNT = 4005 +DATA_CMD_MF1_GET_DETECTION_LOG = 4006 +# FIXME: not implemented +DATA_CMD_MF1_GET_DETECTION_ENABLE = 4007 +DATA_CMD_MF1_READ_EMU_BLOCK_DATA = 4008 +DATA_CMD_MF1_GET_EMULATOR_CONFIG = 4009 +# FIXME: not implemented +DATA_CMD_MF1_GET_GEN1A_MODE = 4010 +DATA_CMD_MF1_SET_GEN1A_MODE = 4011 +# FIXME: not implemented +DATA_CMD_MF1_GET_GEN2_MODE = 4012 +DATA_CMD_MF1_SET_GEN2_MODE = 4013 +# FIXME: not implemented +DATA_CMD_MF1_GET_BLOCK_ANTI_COLL_MODE = 4014 +DATA_CMD_MF1_SET_BLOCK_ANTI_COLL_MODE = 4015 +# FIXME: not implemented +DATA_CMD_MF1_GET_WRITE_MODE = 4016 +DATA_CMD_MF1_SET_WRITE_MODE = 4017 +DATA_CMD_HF14A_GET_ANTI_COLL_DATA = 4018 + +DATA_CMD_EM410X_SET_EMU_ID = 5000 +DATA_CMD_EM410X_GET_EMU_ID = 5001 @enum.unique @@ -182,7 +186,7 @@ def __str__(self): return "NTAG 215" elif self == TagSpecificType.TAG_TYPE_NTAG_216: return "NTAG 216" - return "Unknown" + return "Undefined" @enum.unique @@ -212,6 +216,59 @@ def __str__(self): return "None" +@enum.unique +class MifareClassicPrngType(enum.IntEnum): + # the random number of the card response is fixed + STATIC = 0 + # the random number of the card response is weak + WEAK = 1 + # the random number of the card response is unpredictable + HARD = 2 + + @staticmethod + def list(): + return list(map(int, MifareClassicPrngType)) + + def __str__(self): + if self == MifareClassicPrngType.STATIC: + return "Static" + elif self == MifareClassicPrngType.WEAK: + return "Weak" + elif self == MifareClassicPrngType.HARD: + return "Hard" + return "None" + + +@enum.unique +class MifareClassicDarksideStatus(enum.IntEnum): + OK = 0 + # Darkside can't fix NT (PRNG is unpredictable) + CANT_FIX_NT = 1 + # Darkside try to recover a default key + LUCKY_AUTH_OK = 2 + # Darkside can't get tag response enc(nak) + NO_NAK_SENT = 3 + # Darkside running, can't change tag + TAG_CHANGED = 4 + + @staticmethod + def list(): + return list(map(int, MifareClassicDarksideStatus)) + + def __str__(self): + if self == MifareClassicDarksideStatus.OK: + return "Success" + elif self == MifareClassicDarksideStatus.CANT_FIX_NT: + return "Cannot fix NT (unpredictable PRNG)" + elif self == MifareClassicDarksideStatus.LUCKY_AUTH_OK: + return "Try to recover a default key" + elif self == MifareClassicDarksideStatus.NO_NAK_SENT: + return "Cannot get tag response enc(nak)" + elif self == MifareClassicDarksideStatus.TAG_CHANGED: + return "Tag changed during attack" + return "None" + + @enum.unique class ButtonType(enum.IntEnum): # what, you need the doc for button type? maybe chatgpt known... LOL @@ -292,109 +349,160 @@ def __init__(self, chameleon: chameleon_com.ChameleonCom): :param chameleon: chameleon instance, @see chameleon_device.Chameleon """ self.device = chameleon - if not len(self.device.commands): - self.get_device_capabilities() - def get_firmware_version(self) -> int: + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) + def get_app_version(self): """ Get firmware version number(application) """ - resp = self.device.send_cmd_sync(DATA_CMD_GET_APP_VERSION, 0x00) - return int.from_bytes(resp.data, 'little') + resp = self.device.send_cmd_sync(DATA_CMD_GET_APP_VERSION) + if resp.status == chameleon_status.Device.STATUS_DEVICE_SUCCESS: + resp.data = struct.unpack('!BB', resp.data) + # older protocol, must upgrade! + if resp.status == 0 and resp.data == b'\x00\x01': + print("Chameleon does not understand new protocol. Please update firmware") + return chameleon_com.Response(cmd=DATA_CMD_GET_APP_VERSION, + status=chameleon_status.Device.STATUS_NOT_IMPLEMENTED) + return resp - def get_device_chip_id(self) -> str: + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) + def get_device_chip_id(self): """ Get device chip id """ - resp = self.device.send_cmd_sync(DATA_CMD_GET_DEVICE_CHIP_ID, 0x00) - return resp.data.hex() + resp = self.device.send_cmd_sync(DATA_CMD_GET_DEVICE_CHIP_ID) + if resp.status == chameleon_status.Device.STATUS_DEVICE_SUCCESS: + resp.data = resp.data.hex() + return resp - def get_device_address(self) -> str: + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) + def get_device_address(self): """ Get device address """ - resp = self.device.send_cmd_sync(DATA_CMD_GET_DEVICE_ADDRESS, 0x00) - return resp.data[::-1].hex() + resp = self.device.send_cmd_sync(DATA_CMD_GET_DEVICE_ADDRESS) + if resp.status == chameleon_status.Device.STATUS_DEVICE_SUCCESS: + resp.data = resp.data.hex() + return resp + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) def get_git_version(self) -> str: - resp = self.device.send_cmd_sync(DATA_CMD_GET_GIT_VERSION, 0x00) - return resp.data.decode('utf-8') + resp = self.device.send_cmd_sync(DATA_CMD_GET_GIT_VERSION) + if resp.status == chameleon_status.Device.STATUS_DEVICE_SUCCESS: + resp.data = resp.data.decode('utf-8') + return resp + + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) + def get_device_mode(self): + resp = self.device.send_cmd_sync(DATA_CMD_GET_DEVICE_MODE) + if resp.status == chameleon_status.Device.STATUS_DEVICE_SUCCESS: + resp.data, = struct.unpack('!?', resp.data) + return resp - def is_reader_device_mode(self) -> bool: + def is_device_reader_mode(self) -> bool: """ Get device mode, reader or tag :return: True is reader mode, else tag mode """ - resp = self.device.send_cmd_sync(DATA_CMD_GET_DEVICE_MODE, 0x00) - return True if resp.data[0] == 1 else False + return self.get_device_mode() # Note: Will return NOT_IMPLEMENTED if one tries to set reader mode on Lite @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) - def set_reader_device_mode(self, reader_mode: bool = True): + def change_device_mode(self, mode): + data = struct.pack('!B', mode) + return self.device.send_cmd_sync(DATA_CMD_CHANGE_DEVICE_MODE, data) + + def set_device_reader_mode(self, reader_mode: bool = True): """ Change device mode, reader or tag :param reader_mode: True if reader mode, False if tag mode. :return: """ - return self.device.send_cmd_sync(DATA_CMD_CHANGE_MODE, 0x00, 0x0001 if reader_mode else 0x0000) + self.change_device_mode(reader_mode) @expect_response(chameleon_status.Device.HF_TAG_OK) - def scan_tag_14a(self): + def hf14a_scan(self): """ 14a tags in the scanning field :return: """ - return self.device.send_cmd_sync(DATA_CMD_SCAN_14A_TAG, 0x00) + resp = self.device.send_cmd_sync(DATA_CMD_HF14A_SCAN) + if resp.status == chameleon_status.Device.HF_TAG_OK: + # uidlen[1]|uid[uidlen]|atqa[2]|sak[1]|atslen[1]|ats[atslen] + offset = 0 + data = [] + while offset < len(resp.data): + uidlen, = struct.unpack_from('!B', resp.data, offset) + offset += struct.calcsize('!B') + uid, atqa, sak, atslen = struct.unpack_from(f'!{uidlen}s2s1sB', resp.data, offset) + offset += struct.calcsize(f'!{uidlen}s2s1sB') + ats, = struct.unpack_from(f'!{atslen}s', resp.data, offset) + offset += struct.calcsize(f'!{atslen}s') + data.append({'uid': uid, 'atqa': atqa, 'sak': sak, 'ats': ats}) + resp.data = data + return resp - def detect_mf1_support(self): + @expect_response(chameleon_status.Device.HF_TAG_OK) + def mf1_detect_support(self): """ Detect whether it is mifare classic label :return: """ - return self.device.send_cmd_sync(DATA_CMD_MF1_SUPPORT_DETECT, 0x00) + resp = self.device.send_cmd_sync(DATA_CMD_MF1_DETECT_SUPPORT) + if resp.status == chameleon_status.Device.HF_TAG_OK: + resp.data, = struct.unpack('!?', resp.data) + return resp - def detect_mf1_nt_level(self): + @expect_response(chameleon_status.Device.HF_TAG_OK) + def mf1_detect_prng(self): """ detect mifare Class of classic nt vulnerabilities :return: """ - return self.device.send_cmd_sync(DATA_CMD_MF1_NT_LEVEL_DETECT, 0x00) + resp = self.device.send_cmd_sync(DATA_CMD_MF1_DETECT_PRNG) + if resp.status == chameleon_status.Device.HF_TAG_OK: + resp.data = resp.data[0] + return resp - def detect_darkside_support(self): + @expect_response(chameleon_status.Device.HF_TAG_OK) + def mf1_detect_darkside_support(self): """ Check if the card is vulnerable to mifare classic darkside attack :return: """ - return self.device.send_cmd_sync(DATA_CMD_MF1_DARKSIDE_DETECT, 0x00, None, timeout=20) + resp = self.device.send_cmd_sync(DATA_CMD_MF1_DETECT_DARKSIDE, timeout=20) + if resp.status == chameleon_status.Device.HF_TAG_OK: + resp.data = resp.data[0] + return resp @expect_response(chameleon_status.Device.HF_TAG_OK) - def detect_nt_distance(self, block_known, type_known, key_known): + def mf1_detect_nt_dist(self, block_known, type_known, key_known): """ Detect the random number distance of the card :return: """ - data = bytearray() - data.append(type_known) - data.append(block_known) - data.extend(key_known) - return self.device.send_cmd_sync(DATA_CMD_MF1_NT_DIST_DETECT, 0x00, data) + data = struct.pack('!BB6s', type_known, block_known, key_known) + resp = self.device.send_cmd_sync(DATA_CMD_MF1_DETECT_NT_DIST, data) + if resp.status == chameleon_status.Device.HF_TAG_OK: + uid, dist = struct.unpack('!II', resp.data) + resp.data = {'uid': uid, 'dist': dist} + return resp @expect_response(chameleon_status.Device.HF_TAG_OK) - def acquire_nested(self, block_known, type_known, key_known, block_target, type_target): + def mf1_nested_acquire(self, block_known, type_known, key_known, block_target, type_target): """ Collect the key NT parameters needed for Nested decryption :return: """ - data = bytearray() - data.append(type_known) - data.append(block_known) - data.extend(key_known) - data.append(type_target) - data.append(block_target) - return self.device.send_cmd_sync(DATA_CMD_MF1_NESTED_ACQUIRE, 0x00, data) + data = struct.pack('!BB6sBB', type_known, block_known, key_known, type_target, block_target) + resp = self.device.send_cmd_sync(DATA_CMD_MF1_NESTED_ACQUIRE, data) + if resp.status == chameleon_status.Device.HF_TAG_OK: + resp.data = [{'nt': nt, 'nt_enc': nt_enc, 'par': par} + for nt, nt_enc, par in struct.iter_unpack('!IIB', resp.data)] + return resp @expect_response(chameleon_status.Device.HF_TAG_OK) - def acquire_darkside(self, block_target, type_target, first_recover: int or bool, sync_max): + def mf1_darkside_acquire(self, block_target, type_target, first_recover: int or bool, sync_max): """ Collect the key parameters needed for Darkside decryption :param block_target: @@ -403,17 +511,18 @@ def acquire_darkside(self, block_target, type_target, first_recover: int or bool :param sync_max: :return: """ - data = bytearray() - data.append(type_target) - data.append(block_target) - if isinstance(first_recover, bool): - first_recover = 0x01 if first_recover else 0x00 - data.append(first_recover) - data.append(sync_max) - return self.device.send_cmd_sync(DATA_CMD_MF1_DARKSIDE_ACQUIRE, 0x00, data, timeout=sync_max + 5) + data = struct.pack('!BBBB', type_target, block_target, first_recover, sync_max) + resp = self.device.send_cmd_sync(DATA_CMD_MF1_DARKSIDE_ACQUIRE, data, timeout=sync_max + 5) + if resp.status == chameleon_status.Device.HF_TAG_OK: + if resp.data[0] == MifareClassicDarksideStatus.OK: + darkside_status, uid, nt1, par, ks1, nr, ar = struct.unpack('!BIIIIII', resp.data) + resp.data = (darkside_status, {'uid': uid, 'nt1': nt1, 'par': par, 'ks1': ks1, 'nr': nr, 'ar': ar}) + else: + resp.data = (resp.data[0],) + return resp @expect_response([chameleon_status.Device.HF_TAG_OK, chameleon_status.Device.MF_ERR_AUTH]) - def auth_mf1_key(self, block, type_value, key): + def mf1_auth_one_key_block(self, block, type_value, key): """ Verify the mf1 key, only verify the specified type of key for a single sector :param block: @@ -421,14 +530,13 @@ def auth_mf1_key(self, block, type_value, key): :param key: :return: """ - data = bytearray() - data.append(type_value) - data.append(block) - data.extend(key) - return self.device.send_cmd_sync(DATA_CMD_MF1_CHECK_ONE_KEY_BLOCK, 0x00, data) + data = struct.pack('!BB6s', type_value, block, key) + resp = self.device.send_cmd_sync(DATA_CMD_MF1_AUTH_ONE_KEY_BLOCK, data) + resp.data = resp.status == chameleon_status.Device.HF_TAG_OK + return resp @expect_response(chameleon_status.Device.HF_TAG_OK) - def read_mf1_block(self, block, type_value, key): + def mf1_read_one_block(self, block, type_value, key): """ read one mf1 block :param block: @@ -436,14 +544,11 @@ def read_mf1_block(self, block, type_value, key): :param key: :return: """ - data = bytearray() - data.append(type_value) - data.append(block) - data.extend(key) - return self.device.send_cmd_sync(DATA_CMD_MF1_READ_ONE_BLOCK, 0x00, data) + data = struct.pack('!BB6s', type_value, block, key) + return self.device.send_cmd_sync(DATA_CMD_MF1_READ_ONE_BLOCK, data) @expect_response(chameleon_status.Device.HF_TAG_OK) - def write_mf1_block(self, block, type_value, key, block_data): + def mf1_write_one_block(self, block, type_value, key, block_data): """ Write mf1 single block :param block: @@ -452,64 +557,66 @@ def write_mf1_block(self, block, type_value, key, block_data): :param block_data: :return: """ - data = bytearray() - data.append(type_value) - data.append(block) - data.extend(key) - data.extend(block_data) - return self.device.send_cmd_sync(DATA_CMD_MF1_WRITE_ONE_BLOCK, 0x00, data) + data = struct.pack('!BB6s16s', type_value, block, key, block_data) + resp = self.device.send_cmd_sync(DATA_CMD_MF1_WRITE_ONE_BLOCK, data) + resp.data = resp.status == chameleon_status.Device.HF_TAG_OK + return resp @expect_response(chameleon_status.Device.LF_TAG_OK) - def read_em_410x(self): + def em410x_scan(self): """ Read the card number of EM410X :return: """ - return self.device.send_cmd_sync(DATA_CMD_SCAN_EM410X_TAG, 0x00) + return self.device.send_cmd_sync(DATA_CMD_EM410X_SCAN) @expect_response(chameleon_status.Device.LF_TAG_OK) - def write_em_410x_to_t55xx(self, id_bytes: bytearray): + def em410x_write_to_t55xx(self, id_bytes: bytes): """ Write EM410X card number into T55XX :param id_bytes: ID card number :return: """ - new_key = [0x20, 0x20, 0x66, 0x66] - old_keys = [[0x51, 0x24, 0x36, 0x48], [0x19, 0x92, 0x04, 0x27]] + new_key = b'\x20\x20\x66\x66' + old_keys = [b'\x51\x24\x36\x48', b'\x19\x92\x04\x27'] if len(id_bytes) != 5: raise ValueError("The id bytes length must equal 5") - data = bytearray() - data.extend(id_bytes) - data.extend(new_key) - for key in old_keys: - data.extend(key) - return self.device.send_cmd_sync(DATA_CMD_WRITE_EM410X_TO_T5577, 0x00, data) + data = struct.pack(f'!5s4s{4*len(old_keys)}s', id_bytes, new_key, b''.join(old_keys)) + return self.device.send_cmd_sync(DATA_CMD_EM410X_WRITE_TO_T55XX, data) + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) def get_slot_info(self): """ Get slots info :return: """ - return self.device.send_cmd_sync(DATA_CMD_GET_SLOT_INFO, 0x00) + resp = self.device.send_cmd_sync(DATA_CMD_GET_SLOT_INFO) + if resp.status == chameleon_status.Device.STATUS_DEVICE_SUCCESS: + resp.data = [struct.unpack_from('!HH', resp.data, i) + for i in range(0, len(resp.data), struct.calcsize('!HH'))] + return resp + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) def get_active_slot(self): """ Get selected slot :return: """ - return self.device.send_cmd_sync(DATA_CMD_GET_ACTIVE_SLOT, 0x00) + resp = self.device.send_cmd_sync(DATA_CMD_GET_ACTIVE_SLOT) + if resp.status == chameleon_status.Device.STATUS_DEVICE_SUCCESS: + resp.data = resp.data[0] + return resp @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) - def set_slot_activated(self, slot_index: SlotNumber): + def set_active_slot(self, slot_index: SlotNumber): """ Set the card slot currently active for use :param slot_index: Card slot index :return: """ # SlotNumber() will raise error for us if slot_index not in slot range - data = bytearray() - data.append(SlotNumber.to_fw(slot_index)) - return self.device.send_cmd_sync(DATA_CMD_SET_SLOT_ACTIVATED, 0x00, data) + data = struct.pack('!B', SlotNumber.to_fw(slot_index)) + return self.device.send_cmd_sync(DATA_CMD_SET_ACTIVE_SLOT, data) @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) def set_slot_tag_type(self, slot_index: SlotNumber, tag_type: TagSpecificType): @@ -522,10 +629,8 @@ def set_slot_tag_type(self, slot_index: SlotNumber, tag_type: TagSpecificType): :return: """ # SlotNumber() will raise error for us if slot_index not in slot range - data = bytearray() - data.append(SlotNumber.to_fw(slot_index)) - data.append(tag_type) - return self.device.send_cmd_sync(DATA_CMD_SET_SLOT_TAG_TYPE, 0x00, data) + data = struct.pack('!BH', SlotNumber.to_fw(slot_index), tag_type) + return self.device.send_cmd_sync(DATA_CMD_SET_SLOT_TAG_TYPE, data) @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) def delete_slot_sense_type(self, slot_index: SlotNumber, sense_type: TagSenseType): @@ -535,8 +640,8 @@ def delete_slot_sense_type(self, slot_index: SlotNumber, sense_type: TagSenseTyp :param sense_type: Sense type to disable :return: """ - return self.device.send_cmd_sync(DATA_CMD_DELETE_SLOT_SENSE_TYPE, 0x00, - bytearray([SlotNumber.to_fw(slot_index), sense_type])) + data = struct.pack('!BB', SlotNumber.to_fw(slot_index), sense_type) + return self.device.send_cmd_sync(DATA_CMD_DELETE_SLOT_SENSE_TYPE, data) @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) def set_slot_data_default(self, slot_index: SlotNumber, tag_type: TagSpecificType): @@ -548,13 +653,11 @@ def set_slot_data_default(self, slot_index: SlotNumber, tag_type: TagSpecificTyp :return: """ # SlotNumber() will raise error for us if slot_index not in slot range - data = bytearray() - data.append(SlotNumber.to_fw(slot_index)) - data.append(tag_type) - return self.device.send_cmd_sync(DATA_CMD_SET_SLOT_DATA_DEFAULT, 0x00, data) + data = struct.pack('!BH', SlotNumber.to_fw(slot_index), tag_type) + return self.device.send_cmd_sync(DATA_CMD_SET_SLOT_DATA_DEFAULT, data) @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) - def set_slot_enable(self, slot_index: SlotNumber, enable: bool): + def set_slot_enable(self, slot_index: SlotNumber, enabled: bool): """ Set whether the specified card slot is enabled :param slot_index: Card slot number @@ -562,59 +665,79 @@ def set_slot_enable(self, slot_index: SlotNumber, enable: bool): :return: """ # SlotNumber() will raise error for us if slot_index not in slot range - data = bytearray() - data.append(SlotNumber.to_fw(slot_index)) - data.append(0x01 if enable else 0x00) - return self.device.send_cmd_sync(DATA_CMD_SET_SLOT_ENABLE, 0X00, data) + data = struct.pack('!BB', SlotNumber.to_fw(slot_index), enabled) + return self.device.send_cmd_sync(DATA_CMD_SET_SLOT_ENABLE, data) @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) - def set_em410x_sim_id(self, id_bytes: bytearray): + def em410x_set_emu_id(self, id: bytes): """ Set the card number simulated by EM410x :param id_bytes: byte of the card number :return: """ - if len(id_bytes) != 5: + if len(id) != 5: raise ValueError("The id bytes length must equal 5") - return self.device.send_cmd_sync(DATA_CMD_SET_EM410X_EMU_ID, 0x00, id_bytes) + data = struct.pack('5s', id) + return self.device.send_cmd_sync(DATA_CMD_EM410X_SET_EMU_ID, data) - def get_em410x_sim_id(self): + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) + def em410x_get_emu_id(self): """ Get the simulated EM410x card id """ - return self.device.send_cmd_sync(DATA_CMD_GET_EM410X_EMU_ID, 0x00) + return self.device.send_cmd_sync(DATA_CMD_EM410X_GET_EMU_ID) @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) - def set_mf1_detection_enable(self, enable: bool): + def mf1_set_detection_enable(self, enabled: bool): """ Set whether to enable the detection of the current card slot :param enable: Whether to enable :return: """ - data = bytearray() - data.append(0x01 if enable else 0x00) - return self.device.send_cmd_sync(DATA_CMD_SET_MF1_DETECTION_ENABLE, 0x00, data) + data = struct.pack('!B', enabled) + return self.device.send_cmd_sync(DATA_CMD_MF1_SET_DETECTION_ENABLE, data) - def get_mf1_detection_count(self): + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) + def mf1_get_detection_count(self): """ Get the statistics of the current detection records :return: """ - return self.device.send_cmd_sync(DATA_CMD_GET_MF1_DETECTION_COUNT, 0x00) + resp = self.device.send_cmd_sync(DATA_CMD_MF1_GET_DETECTION_COUNT) + if resp.status == chameleon_status.Device.STATUS_DEVICE_SUCCESS: + resp.data, = struct.unpack('!I', resp.data) + return resp @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) - def get_mf1_detection_log(self, index: int): + def mf1_get_detection_log(self, index: int): """ Get detection logs from the specified index position :param index: start index :return: """ - data = bytearray() - data.extend(index.to_bytes(4, "big", signed=False)) - return self.device.send_cmd_sync(DATA_CMD_GET_MF1_DETECTION_RESULT, 0x00, data) + data = struct.pack('!I', index) + resp = self.device.send_cmd_sync(DATA_CMD_MF1_GET_DETECTION_LOG, data) + if resp.status == chameleon_status.Device.STATUS_DEVICE_SUCCESS: + # convert + result_list = [] + pos = 0 + while pos < len(resp.data): + block, bitfield, uid, nt, nr, ar = struct.unpack_from('!BB4s4s4s4s', resp.data, pos) + result_list.append({ + 'block': block, + 'type': ['A', 'B'][bitfield & 0x01], + 'is_nested': bool(bitfield & 0x02), + 'uid': uid.hex(), + 'nt': nt.hex(), + 'nr': nr.hex(), + 'ar': ar.hex() + }) + pos += struct.calcsize('!BB4s4s4s4s') + resp.data = result_list + return resp @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) - def set_mf1_block_data(self, block_start: int, block_data: bytearray): + def mf1_write_emu_block_data(self, block_start: int, block_data: bytes): """ Set the block data of the analog card of MF1 :param block_start: Start setting the location of block data, including this location @@ -622,61 +745,57 @@ def set_mf1_block_data(self, block_start: int, block_data: bytearray): can contain multiple block data, automatically from block_start increment :return: """ - data = bytearray() - data.append(block_start & 0xFF) - data.extend(block_data) - return self.device.send_cmd_sync(DATA_CMD_LOAD_MF1_EMU_BLOCK_DATA, 0x00, data) + data = struct.pack(f'!B{len(block_data)}s', block_start, block_data) + return self.device.send_cmd_sync(DATA_CMD_MF1_WRITE_EMU_BLOCK_DATA, data) - def get_mf1_block_data(self, block_start: int, block_count: int): + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) + def mf1_read_emu_block_data(self, block_start: int, block_count: int): """ Gets data for selected block range """ - return self.device.send_cmd_sync(DATA_CMD_READ_MF1_EMU_BLOCK_DATA, 0x00, bytearray([block_start, block_count])) + data = struct.pack('!BB', block_start, block_count) + return self.device.send_cmd_sync(DATA_CMD_MF1_READ_EMU_BLOCK_DATA, data) @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) - def set_mf1_anti_collision_res(self, sak: bytearray, atqa: bytearray, uid: bytearray): + def hf14a_set_anti_coll_data(self, uid: bytes, atqa: bytes, sak: bytes, ats: bytes = b''): """ - Set the anti-collision resource information of the MF1 analog card + Set anti-collision data of current HF slot (UID/SAK/ATQA/ATS) + :param uid: uid bytes + :param atqa: atqa bytes :param sak: sak bytes - :param atqa: atqa array - :param uid: card number array + :param ats: ats bytes (optional) :return: """ - data = bytearray() - data.extend(sak) - data.extend(atqa) - data.extend(uid) - return self.device.send_cmd_sync(DATA_CMD_SET_MF1_ANTI_COLLISION_RES, 0X00, data) + data = struct.pack(f'!B{len(uid)}s2s1sB{len(ats)}s', len(uid), uid, atqa, sak, len(ats), ats) + return self.device.send_cmd_sync(DATA_CMD_HF14A_SET_ANTI_COLL_DATA, data) @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) - def set_slot_tag_nick_name(self, slot: SlotNumber, sense_type: TagSenseType, name: bytes): + def set_slot_tag_nick(self, slot: SlotNumber, sense_type: TagSenseType, name: bytes): """ - Set the anti-collision resource information of the MF1 analog card + Set the nick name of the slot :param slot: Card slot number :param sense_type: field type :param name: Card slot nickname :return: """ # SlotNumber() will raise error for us if slot not in slot range - data = bytearray() - data.extend([SlotNumber.to_fw(slot), sense_type]) - data.extend(name) - return self.device.send_cmd_sync(DATA_CMD_SET_SLOT_TAG_NICK, 0x00, data) + data = struct.pack(f'!BB{len(name)}s', SlotNumber.to_fw(slot), sense_type, name) + return self.device.send_cmd_sync(DATA_CMD_SET_SLOT_TAG_NICK, data) @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) - def get_slot_tag_nick_name(self, slot: SlotNumber, sense_type: TagSenseType): + def get_slot_tag_nick(self, slot: SlotNumber, sense_type: TagSenseType): """ - Set the anti-collision resource information of the MF1 analog card + Get the nick name of the slot :param slot: Card slot number :param sense_type: field type :return: """ # SlotNumber() will raise error for us if slot not in slot range - data = bytearray() - data.extend([SlotNumber.to_fw(slot), sense_type]) - return self.device.send_cmd_sync(DATA_CMD_GET_SLOT_TAG_NICK, 0x00, data) + data = struct.pack('!BB', SlotNumber.to_fw(slot), sense_type) + return self.device.send_cmd_sync(DATA_CMD_GET_SLOT_TAG_NICK, data) - def get_mf1_emulator_settings(self): + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) + def mf1_get_emulator_config(self): """ Get array of Mifare Classic emulators settings: [0] - mf1_is_detection_enable (mfkey32) @@ -686,118 +805,163 @@ def get_mf1_emulator_settings(self): [4] - mf1_get_write_mode :return: """ - return self.device.send_cmd_sync(DATA_CMD_GET_MF1_EMULATOR_CONFIG, 0x00) + resp = self.device.send_cmd_sync(DATA_CMD_MF1_GET_EMULATOR_CONFIG) + if resp.status == chameleon_status.Device.STATUS_DEVICE_SUCCESS: + b1, b2, b3, b4, b5 = struct.unpack('!????B', resp.data) + resp.data = {'detection': b1, + 'gen1a_mode': b2, + 'gen2_mode': b3, + 'block_anti_coll_mode': b4, + 'write_mode': b5} + return resp - def set_mf1_gen1a_mode(self, enabled: bool): + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) + def mf1_set_gen1a_mode(self, enabled: bool): """ Set gen1a magic mode """ - return self.device.send_cmd_sync(DATA_CMD_SET_MF1_GEN1A_MODE, 0x00, bytearray([1 if enabled else 0])) + data = struct.pack('!B', enabled) + return self.device.send_cmd_sync(DATA_CMD_MF1_SET_GEN1A_MODE, data) - def set_mf1_gen2_mode(self, enabled: bool): + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) + def mf1_set_gen2_mode(self, enabled: bool): """ Set gen2 magic mode """ - return self.device.send_cmd_sync(DATA_CMD_SET_MF1_GEN2_MODE, 0x00, bytearray([1 if enabled else 0])) + data = struct.pack('!B', enabled) + return self.device.send_cmd_sync(DATA_CMD_MF1_SET_GEN2_MODE, data) - def set_mf1_block_anti_coll_mode(self, enabled: bool): + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) + def mf1_set_block_anti_coll_mode(self, enabled: bool): """ Set 0 block anti-collision data """ - return self.device.send_cmd_sync(DATA_CMD_SET_MF1_USE_FIRST_BLOCK_COLL, 0x00, bytearray([1 if enabled else 0])) + data = struct.pack('!B', enabled) + return self.device.send_cmd_sync(DATA_CMD_MF1_SET_BLOCK_ANTI_COLL_MODE, data) - def set_mf1_write_mode(self, mode: int): + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) + def mf1_set_write_mode(self, mode: int): """ Set write mode """ - return self.device.send_cmd_sync(DATA_CMD_SET_MF1_WRITE_MODE, 0x00, bytearray([mode])) + data = struct.pack('!B', mode) + return self.device.send_cmd_sync(DATA_CMD_MF1_SET_WRITE_MODE, data) - def update_slot_data_config(self): + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) + def slot_data_config_save(self): """ Update the configuration and data of the card slot to flash. :return: """ - return self.device.send_cmd_sync(DATA_CMD_SLOT_DATA_CONFIG_SAVE, 0x00) + return self.device.send_cmd_sync(DATA_CMD_SLOT_DATA_CONFIG_SAVE) - def enter_dfu_mode(self): + def enter_bootloader(self): """ Reboot into DFU mode (bootloader) :return: """ - return self.device.send_cmd_auto(DATA_CMD_ENTER_BOOTLOADER, 0x00, close=True) + self.device.send_cmd_auto(DATA_CMD_ENTER_BOOTLOADER, close=True) - def get_settings_animation(self): + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) + def get_animation_mode(self): """ Get animation mode value """ - return self.device.send_cmd_sync(DATA_CMD_GET_ANIMATION_MODE, 0x00) + resp = self.device.send_cmd_sync(DATA_CMD_GET_ANIMATION_MODE) + if resp.status == chameleon_status.Device.STATUS_DEVICE_SUCCESS: + resp.data = resp.data[0] + return resp + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) def get_enabled_slots(self): """ Get enabled slots """ - return self.device.send_cmd_sync(DATA_CMD_GET_ENABLED_SLOTS, 0x00) + return self.device.send_cmd_sync(DATA_CMD_GET_ENABLED_SLOTS) - def set_settings_animation(self, value: int): + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) + def set_animation_mode(self, value: int): """ Set animation mode value """ - return self.device.send_cmd_sync(DATA_CMD_SET_ANIMATION_MODE, 0x00, bytearray([value])) + data = struct.pack('!B', value) + return self.device.send_cmd_sync(DATA_CMD_SET_ANIMATION_MODE, data) + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) def reset_settings(self): """ Reset settings stored in flash memory """ - return self.device.send_cmd_sync(DATA_CMD_RESET_SETTINGS, 0x00) + resp = self.device.send_cmd_sync(DATA_CMD_RESET_SETTINGS) + resp.data = resp.status == chameleon_status.Device.STATUS_DEVICE_SUCCESS + return resp - def store_settings(self): + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) + def save_settings(self): """ Store settings to flash memory """ - return self.device.send_cmd_sync(DATA_CMD_SAVE_SETTINGS, 0x00) + resp = self.device.send_cmd_sync(DATA_CMD_SAVE_SETTINGS) + resp.data = resp.status == chameleon_status.Device.STATUS_DEVICE_SUCCESS + return resp - def factory_reset(self): + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) + def wipe_fds(self): """ Reset to factory settings """ - ret = self.device.send_cmd_sync(DATA_CMD_WIPE_FDS, 0x00) + resp = self.device.send_cmd_sync(DATA_CMD_WIPE_FDS) + resp.data = resp.status == chameleon_status.Device.STATUS_DEVICE_SUCCESS self.device.close() - return ret + return resp @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) - def battery_information(self): + def get_battery_info(self): """ Get battery info """ - return self.device.send_cmd_sync(DATA_CMD_GET_BATTERY_INFO, 0x00) + resp = self.device.send_cmd_sync(DATA_CMD_GET_BATTERY_INFO) + if resp.status == chameleon_status.Device.STATUS_DEVICE_SUCCESS: + resp.data = struct.unpack('!HB', resp.data) + return resp @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) - def get_button_press_fun(self, button: ButtonType): + def get_button_press_config(self, button: ButtonType): """ Get config of button press function """ - return self.device.send_cmd_sync(DATA_CMD_GET_BUTTON_PRESS_CONFIG, 0x00, bytearray([button])) + data = struct.pack('!B', button) + resp = self.device.send_cmd_sync(DATA_CMD_GET_BUTTON_PRESS_CONFIG, data) + if resp.status == chameleon_status.Device.STATUS_DEVICE_SUCCESS: + resp.data = resp.data[0] + return resp @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) - def set_button_press_fun(self, button: ButtonType, function: ButtonPressFunction): + def set_button_press_config(self, button: ButtonType, function: ButtonPressFunction): """ Set config of button press function """ - return self.device.send_cmd_sync(DATA_CMD_SET_BUTTON_PRESS_CONFIG, 0x00, bytearray([button, function])) + data = struct.pack('!BB', button, function) + return self.device.send_cmd_sync(DATA_CMD_SET_BUTTON_PRESS_CONFIG, data) @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) - def get_long_button_press_fun(self, button: ButtonType): + def get_long_button_press_config(self, button: ButtonType): """ - Get config of button press function + Get config of long button press function """ - return self.device.send_cmd_sync(DATA_CMD_GET_LONG_BUTTON_PRESS_CONFIG, 0x00, bytearray([button])) + data = struct.pack('!B', button) + resp = self.device.send_cmd_sync(DATA_CMD_GET_LONG_BUTTON_PRESS_CONFIG, data) + if resp.status == chameleon_status.Device.STATUS_DEVICE_SUCCESS: + resp.data = resp.data[0] + return resp @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) - def set_long_button_press_fun(self, button: ButtonType, function: ButtonPressFunction): + def set_long_button_press_config(self, button: ButtonType, function: ButtonPressFunction): """ - Set config of button press function + Set config of long button press function """ - return self.device.send_cmd_sync(DATA_CMD_SET_LONG_BUTTON_PRESS_CONFIG, 0x00, bytearray([button, function])) + data = struct.pack('!BB', button, function) + return self.device.send_cmd_sync(DATA_CMD_SET_LONG_BUTTON_PRESS_CONFIG, data) @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) def set_ble_connect_key(self, key: str): @@ -810,44 +974,53 @@ def set_ble_connect_key(self, key: str): if len(data_bytes) != 6: raise ValueError("The ble connect key length must be 6") - return self.device.send_cmd_sync(DATA_CMD_SET_BLE_CONNECT_KEY_CONFIG, 0x00, data_bytes) + data = struct.pack('6s', data_bytes) + return self.device.send_cmd_sync(DATA_CMD_SET_BLE_PAIRING_KEY, data) - def get_ble_connect_key(self): + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) + def get_ble_pairing_key(self): """ Get config of ble connect key """ - return self.device.send_cmd_sync(DATA_CMD_GET_BLE_CONNECT_KEY_CONFIG, 0x00, None) + return self.device.send_cmd_sync(DATA_CMD_GET_BLE_PAIRING_KEY) + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) def delete_ble_all_bonds(self): """ From peer manager delete all bonds. """ - return self.device.send_cmd_sync(DATA_CMD_DELETE_ALL_BLE_BONDS, 0x00, None) + return self.device.send_cmd_sync(DATA_CMD_DELETE_ALL_BLE_BONDS) + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) def get_device_capabilities(self): """ - Get (and set) commands that client understands + Get list of commands that client understands """ - - commands = [] - try: - ret = self.device.send_cmd_sync(DATA_CMD_GET_DEVICE_CAPABILITIES, 0x00) - self.device.commands = struct.unpack(f"{len(ret.data) // 2}H", ret.data) - except: - print("Chameleon doesn't understand get capabilities command. Please update firmware") - - return commands + resp = self.device.send_cmd_sync(DATA_CMD_GET_DEVICE_CAPABILITIES) + except chameleon_com.CMDInvalidException: + print("Chameleon does not understand get_device_capabilities command. Please update firmware") + return chameleon_com.Response(cmd=DATA_CMD_GET_DEVICE_CAPABILITIES, + status=chameleon_status.Device.STATUS_NOT_IMPLEMENTED) + else: + if resp.status == chameleon_status.Device.STATUS_DEVICE_SUCCESS: + resp.data = [x[0] for x in struct.iter_unpack('!H', resp.data)] + return resp - def get_device_type(self): + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) + def get_device_model(self): """ - Get device type + Get device model 0 - Chameleon Ultra 1 - Chameleon Lite """ - return self.device.send_cmd_sync(DATA_CMD_GET_DEVICE, 0x00).data[0] + resp = self.device.send_cmd_sync(DATA_CMD_GET_DEVICE_MODEL) + if resp.status == chameleon_status.Device.STATUS_DEVICE_SUCCESS: + resp.data = resp.data[0] + return resp + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) def get_device_settings(self): """ Get all possible settings @@ -858,33 +1031,63 @@ def get_device_settings(self): settings[3] = settings_get_button_press_config('B'); // short B button press mode settings[4] = settings_get_long_button_press_config('A'); // long A button press mode settings[5] = settings_get_long_button_press_config('B'); // long B button press mode - settings[6] = settings_get_ble_pairing_enable(); // is device require pairing - settings[7:13] = settings_get_ble_connect_key(); // BLE connection key - """ - data = self.device.send_cmd_sync(DATA_CMD_GET_SETTINGS, 0x00).data - if data[0] != CURRENT_VERSION_SETTINGS: - raise ValueError("Settings version in app doesn't match Chameleon response. Upgrade client or Chameleon " - "firmware") - return data + settings[6] = settings_get_ble_pairing_enable(); // does device require pairing + settings[7:13] = settings_get_ble_pairing_key(); // BLE pairing key + """ + resp = self.device.send_cmd_sync(DATA_CMD_GET_DEVICE_SETTINGS) + if resp.status == chameleon_status.Device.STATUS_DEVICE_SUCCESS: + if resp.data[0] > CURRENT_VERSION_SETTINGS: + raise ValueError("Settings version in app older than Chameleon. " + "Please upgrade client") + if resp.data[0] < CURRENT_VERSION_SETTINGS: + raise ValueError("Settings version in app newer than Chameleon. " + "Please upgrade Chameleon firmware") + settings_version, animation_mode, btn_press_A, btn_press_B, btn_long_press_A, btn_long_press_B, ble_pairing_enable, ble_pairing_key = struct.unpack( + '!BBBBBBB6s', resp.data) + resp.data = {'settings_version': settings_version, + 'animation_mode': animation_mode, + 'btn_press_A': btn_press_A, + 'btn_press_B': btn_press_B, + 'btn_long_press_A': btn_long_press_A, + 'btn_long_press_B': btn_long_press_B, + 'ble_pairing_enable': ble_pairing_enable, + 'ble_pairing_key': ble_pairing_key} + return resp - def get_mf1_anti_coll_data(self): + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) + def hf14a_get_anti_coll_data(self): """ - Get data from current slot (UID/SAK/ATQA) + Get anti-collision data from current HF slot (UID/SAK/ATQA/ATS) :return: """ - return self.device.send_cmd_sync(DATA_CMD_GET_MF1_ANTI_COLL_DATA, 0x00) + resp = self.device.send_cmd_sync(DATA_CMD_HF14A_GET_ANTI_COLL_DATA) + if resp.status == chameleon_status.Device.STATUS_DEVICE_SUCCESS and len(resp.data) > 0: + # uidlen[1]|uid[uidlen]|atqa[2]|sak[1]|atslen[1]|ats[atslen] + offset = 0 + uidlen, = struct.unpack_from('!B', resp.data, offset) + offset += struct.calcsize('!B') + uid, atqa, sak, atslen = struct.unpack_from(f'!{uidlen}s2s1sB', resp.data, offset) + offset += struct.calcsize(f'!{uidlen}s2s1sB') + ats, = struct.unpack_from(f'!{atslen}s', resp.data, offset) + offset += struct.calcsize(f'!{atslen}s') + resp.data = {'uid': uid, 'atqa': atqa, 'sak': sak, 'ats': ats} + return resp - def get_ble_pairing_enable(self) -> bool: + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) + def get_ble_pairing_enable(self): """ Is ble pairing enable? :return: True if pairing is enable, False if pairing disabled """ - resp = self.device.send_cmd_sync(DATA_CMD_GET_BLE_PAIRING_ENABLE, 0x00) - return resp.data[0] == 1 - + resp = self.device.send_cmd_sync(DATA_CMD_GET_BLE_PAIRING_ENABLE) + if resp.status == chameleon_status.Device.STATUS_DEVICE_SUCCESS: + resp.data, = struct.unpack('!?', resp.data) + return resp + @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) - def set_ble_pairing_enable(self, enable: bool): - return self.device.send_cmd_sync(DATA_CMD_SET_BLE_PAIRING_ENABLE, 0x00, 1 if enable else 0) + def set_ble_pairing_enable(self, enabled: bool): + data = struct.pack('!B', enabled) + return self.device.send_cmd_sync(DATA_CMD_SET_BLE_PAIRING_ENABLE, data) if __name__ == '__main__': @@ -892,8 +1095,8 @@ def set_ble_pairing_enable(self, enable: bool): dev = chameleon_com.ChameleonCom() dev.open("com19") cml = ChameleonCMD(dev) - ver = cml.get_firmware_version() - print(f"Firmware number of application: {ver}") + ver = cml.get_app_version() + print(f"Firmware number of application: {ver[0]}.{ver[1]}") chip = cml.get_device_chip_id() print(f"Device chip id: {chip}") diff --git a/software/script/chameleon_com.py b/software/script/chameleon_com.py index c309fb1f..023f17e2 100644 --- a/software/script/chameleon_com.py +++ b/software/script/chameleon_com.py @@ -29,7 +29,7 @@ class Response: Chameleon Response Data """ - def __init__(self, cmd, status, data): + def __init__(self, cmd, status, data=b''): self.cmd = cmd self.status = status self.data: bytearray = data @@ -153,37 +153,35 @@ def thread_data_receive(self): if len(data_bytes) > 0: data_byte = data_bytes[0] data_buffer.append(data_byte) - if data_position < 2: # start of frame + if data_position < struct.calcsize('!BB'): # start of frame + lrc1 if data_position == 0: if data_buffer[data_position] != self.data_frame_sof: print("Data frame no sof byte.") data_position = 0 data_buffer.clear() continue - if data_position == 1: - if data_buffer[data_position] != self.lrc_calc(data_buffer[0:1]): + if data_position == struct.calcsize('!B'): + if data_buffer[data_position] != self.lrc_calc(data_buffer[:data_position]): data_position = 0 data_buffer.clear() print("Data frame sof lrc error.") continue - elif data_position == 8: # frame head lrc - if data_buffer[data_position] != self.lrc_calc(data_buffer[0:8]): + elif data_position == struct.calcsize('!BBHHH'): # frame head lrc + if data_buffer[data_position] != self.lrc_calc(data_buffer[:data_position]): data_position = 0 data_buffer.clear() print("Data frame head lrc error.") continue # frame head complete, cache info - data_cmd = struct.unpack(">H", data_buffer[2:4])[0] - data_status = struct.unpack(">H", data_buffer[4:6])[0] - data_length = struct.unpack(">H", data_buffer[6:8])[0] + _, _, data_cmd, data_status, data_length = struct.unpack("!BBHHH", data_buffer[:data_position]) if data_length > self.data_max_length: data_position = 0 data_buffer.clear() - print("Data frame data length too than of max.") + print("Data frame data length larger than max.") continue - elif data_position > 8: # // frame data - if data_position == (8 + data_length + 1): - if data_buffer[data_position] == self.lrc_calc(data_buffer[0:-1]): + elif data_position > struct.calcsize('!BBHHH'): # // frame data + if data_position == (struct.calcsize(f'!BBHHHB{data_length}s')): + if data_buffer[data_position] == self.lrc_calc(data_buffer[:data_position]): # ok, lrc for data is correct. # and we are receive completed # print(f"Buffer data = {data_buffer.hex()}") @@ -193,7 +191,8 @@ def thread_data_receive(self): fn_call = self.wait_response_map[data_cmd]['callback'] else: fn_call = None - data_response = data_buffer[9: 9 + data_length] + data_response = data_buffer[struct.calcsize('!BBHHHB'): + struct.calcsize(f'!BBHHHB{data_length}s')] if callable(fn_call): # delete wait task from map del self.wait_response_map[data_cmd] @@ -204,7 +203,7 @@ def thread_data_receive(self): else: print(f"No task wait process: ${data_cmd}") else: - print("Data frame finally lrc error.") + print("Data frame global lrc error.") data_position = 0 data_buffer.clear() continue @@ -265,36 +264,32 @@ def thread_check_timeout(self): self.wait_response_map[task_cmd]['is_timeout'] = True time.sleep(0.001) - def make_data_frame_bytes(self, cmd: int, status: int, data: bytearray = None) -> bytearray: + def make_data_frame_bytes(self, cmd: int, data: bytearray = None, status: int = 0) -> bytearray: """ Make data frame :return: frame """ - frame = bytearray() - # sof and sof lrc byte - frame.append(self.data_frame_sof) - frame.append(self.lrc_calc(frame[0:1])) - # head info - frame.extend(struct.pack('>H', cmd)) - frame.extend(struct.pack('>H', status)) - frame.extend(struct.pack('>H', len(data) if data is not None else 0)) - frame.append(self.lrc_calc(frame[2:8])) - # data - if data is not None: - frame.extend(data) - # frame lrc - frame.append(self.lrc_calc(frame)) + if data is None: + data = b'' + frame = bytearray(struct.pack(f'!BBHHHB{len(data)}sB', + self.data_frame_sof, 0x00, cmd, status, len(data), 0x00, data, 0x00)) + # lrc1 + frame[struct.calcsize('!B')] = self.lrc_calc(frame[:struct.calcsize('!B')]) + # lrc2 + frame[struct.calcsize('!BBHHH')] = self.lrc_calc(frame[:struct.calcsize('!BBHHH')]) + # lrc3 + frame[struct.calcsize(f'!BBHHHB{len(data)}s')] = self.lrc_calc(frame[:struct.calcsize(f'!BBHHHB{len(data)}s')]) return frame - def send_cmd_auto(self, cmd: int, status: int, data: bytearray = None, callback=None, timeout: int = 3, + def send_cmd_auto(self, cmd: int, data: bytearray = None, status: int = 0, callback=None, timeout: int = 3, close: bool = False): """ Send cmd to device - :param timeout: wait response timeout :param cmd: cmd - :param status: status(optional) + :param data: bytes data (optional) + :param status: status (optional) :param callback: call on response - :param data: bytes data + :param timeout: wait response timeout :param close: close connection after executing :return: """ @@ -303,20 +298,20 @@ def send_cmd_auto(self, cmd: int, status: int, data: bytearray = None, callback= if cmd in self.wait_response_map: del self.wait_response_map[cmd] # make data frame - data_frame = self.make_data_frame_bytes(cmd, status, data) + data_frame = self.make_data_frame_bytes(cmd, data, status) task = {'cmd': cmd, 'frame': data_frame, 'timeout': timeout, 'close': close} if callable(callback): task['callback'] = callback self.send_data_queue.put(task) return self - def send_cmd_sync(self, cmd: int, status: int, data: bytearray or bytes or list or int = None, + def send_cmd_sync(self, cmd: int, data: bytearray or bytes or list or int = None, status: int = 0, timeout: int = 3) -> Response: """ Send cmd to device, and block receive data. :param cmd: cmd - :param status: status(optional) - :param data: bytes data + :param data: bytes data (optional) + :param status: status (optional) :param timeout: wait response timeout :return: response data """ @@ -327,9 +322,8 @@ def send_cmd_sync(self, cmd: int, status: int, data: bytearray or bytes or list if cmd not in self.commands: raise CMDInvalidException(f"This device doesn't declare that it can support this command: {cmd}.\nMake " f"sure firmware is up to date and matches client") - # return Response(cmd=cmd, status=0, data=b"\0" * 32) # forge fake response to not break app # first to send cmd, no callback mode(sync) - self.send_cmd_auto(cmd, status, data, None, timeout) + self.send_cmd_auto(cmd, data, status, None, timeout) # wait cmd start process while cmd not in self.wait_response_map: time.sleep(0.01) @@ -348,5 +342,5 @@ def send_cmd_sync(self, cmd: int, status: int, data: bytearray or bytes or list if __name__ == '__main__': cml = ChameleonCom().open("com19") - resp = cml.send_cmd_sync(0x03E9, 0xBEEF, bytearray([0x01, 0x02])) + resp = cml.send_cmd_sync(0x03E9, bytearray([0x01, 0x02]), 0xBEEF) print(resp) diff --git a/software/script/chameleon_cstruct.py b/software/script/chameleon_cstruct.py deleted file mode 100644 index 14edc5fe..00000000 --- a/software/script/chameleon_cstruct.py +++ /dev/null @@ -1,113 +0,0 @@ -""" - From bytes to c struct parser(Chameleon data response) -""" - - -def bytes_to_u32(byte_array): - """ - bytes array to u32 int - :param byte_array: - :return: - """ - return int.from_bytes(byte_array, byteorder='big', signed=False) - - -def parse_14a_scan_tag_result(data: bytearray): - """ - From bytes parse tag info - :param data: - :return: - """ - return { - 'uid_size': data[10], - 'uid_hex': data[0:data[10]].hex(), - 'sak_hex': hex(data[12]).lstrip('0x').rjust(2, '0'), - 'atqa_hex': data[13:15].hex().upper() - } - - -def parse_nt_distance_detect_result(data: bytearray): - """ - From bytes parse nt distance - :param data: data - :return: - """ - return {'uid': bytes_to_u32(data[0:4]), 'dist': bytes_to_u32(data[4:8])} - - -def parse_nested_nt_acquire_group(data: bytearray): - """ - From bytes parse nested param - :param data: data - :return: - """ - group = [] - if len(data) % 9 != 0: - raise ValueError( - "Nt data length error, except: { nt(4byte), nt_enc(4byte), par(1byte) } * N") - i = 0 - while i < len(data): - group.append({ - 'nt': bytes_to_u32(data[i: i + 4]), - 'nt_enc': bytes_to_u32(data[i + 4: i + 8]), - 'par': data[i + 8] - }) - i += 9 - return group - - -def parse_darkside_acquire_result(data: bytearray): - """ - From bytes parse darkside param - :param data: data - :return: - """ - return { - 'uid': bytes_to_u32(data[0: 4]), - 'nt1': bytes_to_u32(data[4: 8]), - 'par': bytes_to_u32(data[8: 16]), - 'ks1': bytes_to_u32(data[16: 24]), - 'nr': bytes_to_u32(data[24: 28]), - 'ar': bytes_to_u32(data[28: 32]) - } - - -def parse_mf1_detection_result(data: bytearray): - """ - From bytes parse detection param - :param data: data - :return: - """ - # convert - result_list = [] - pos = 0 - while pos < len(data): - result_list.append({ - 'block': data[0 + pos], - 'type': 0x60 + (data[1 + pos] & 0x01), - 'is_nested': True if data[1 + pos] >> 1 & 0x01 == 0x01 else False, - 'uid': data[2 + pos: 6 + pos].hex(), - 'nt': data[6 + pos: 10 + pos].hex(), - 'nr': data[10 + pos: 14 + pos].hex(), - 'ar': data[14 + pos: 18 + pos].hex() - }) - pos += 18 - - # classify - result_map = {} - for item in result_list: - uid = item['uid'] - if uid not in result_map: - result_map[uid] = {} - - block = item['block'] - if block not in result_map[uid]: - result_map[uid][block] = {} - - type_chr = 'A' if item['type'] == 0x60 else 'B' - if type_chr not in result_map[uid][block]: - result_map[uid][block][type_chr] = [] - - result_map[uid][block][type_chr].append(item) - - return result_map diff --git a/software/script/chameleon_status.py b/software/script/chameleon_status.py index c3a9180b..cf820579 100644 --- a/software/script/chameleon_status.py +++ b/software/script/chameleon_status.py @@ -12,6 +12,14 @@ def __contains__(self, item): return True return False + def __getitem__(self, item): + for field in self.__dict__: + val = self.__dict__[field] + if isinstance(val, int): + if val == item: + return field + return False + class Device(metaclass=MetaDevice): HF_TAG_OK = 0x00 # IC card operation is successful @@ -23,20 +31,6 @@ class Device(metaclass=MetaDevice): MF_ERR_AUTH = 0x06 # MF card verification failed HF_ERR_PARITY = 0x07 # IC card parity error - # Darkside, the random number cannot be fixed, this situation may appear on the UID card - DARKSIDE_CANT_FIXED_NT = 0x20 - # Darkside, the direct verification is successful, maybe the key is empty - DARKSIDE_LUCK_AUTH_OK = 0x21 - # Darkside, the card doesn't respond to nack, probably a card that fixes the nack logic bug - DARKSIDE_NACK_NO_SEND = 0x22 - # Darkside, there is a card switching during the running of darkside, - # maybe there is a signal problem, or the two cards really switched quickly - DARKSIDE_TAG_CHANGED = 0x23 - # Nested, it is detected that the random number of the card response is fixed - NESTED_TAG_IS_STATIC = 0x24 - # Nested, detected nonce for card response is unpredictable - NESTED_TAG_IS_HARD = 0x25 - # Some operations with low frequency cards succeeded! LF_TAG_OK = 0x40 # Unable to search for a valid EM410X label @@ -64,13 +58,6 @@ class Device(metaclass=MetaDevice): Device.MF_ERR_AUTH: "HF tag auth fail", Device.HF_ERR_PARITY: "HF tag data parity error", - Device.DARKSIDE_CANT_FIXED_NT: "Darkside Can't select a nt(PRNG is unpredictable)", - Device.DARKSIDE_LUCK_AUTH_OK: "Darkside try to recover a default key", - Device.DARKSIDE_NACK_NO_SEND: "Darkside can't make tag response nack(enc)", - Device.DARKSIDE_TAG_CHANGED: "Darkside running, can't change tag", - Device.NESTED_TAG_IS_STATIC: "StaticNested tag, not weak nested", - Device.NESTED_TAG_IS_HARD: "HardNested tag, not weak nested", - Device.LF_TAG_OK: "LF tag operation succeeded", Device.EM410X_TAG_NO_FOUND: "EM410x tag no found", diff --git a/software/script/chameleon_utils.py b/software/script/chameleon_utils.py index d2e8e85c..bca09b40 100644 --- a/software/script/chameleon_utils.py +++ b/software/script/chameleon_utils.py @@ -54,7 +54,6 @@ def decorator(func): @wraps(func) def error_throwing_func(*args, **kwargs): ret = func(*args, **kwargs) - if ret.status not in accepted_responses: if ret.status in chameleon_status.Device and ret.status in chameleon_status.message: raise UnexpectedResponseError( @@ -62,8 +61,8 @@ def error_throwing_func(*args, **kwargs): else: raise UnexpectedResponseError( f"Unexpected response and unknown status {ret.status}") - - return ret + + return ret.data return error_throwing_func From 782905f2024e33c9e3caf29eb1b55e057e224b28 Mon Sep 17 00:00:00 2001 From: Philippe Teuwen Date: Mon, 18 Sep 2023 17:05:03 +0200 Subject: [PATCH 02/38] fix Darkside --- CHANGELOG.md | 2 + .../src/rfid/reader/hf/mf1_toolbox.c | 43 +++++++++++-------- .../src/rfid/reader/hf/mf1_toolbox.h | 1 + software/script/chameleon_cli_unit.py | 7 ++- software/script/chameleon_cmd.py | 4 +- 5 files changed, 33 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a6919af9..88278320 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ All notable changes to this project will be documented in this file. This project uses the changelog in accordance with [keepchangelog](http://keepachangelog.com/). Please use this to write notable changes, which is not the same as git commit log... ## [unreleased][unreleased] + - Changed Darkside: longer RF field off for reset and longer CLI timeout (@doegox) + - Fixed Darkside: parity handling bug made it low probability to succeed (@doegox) - Changed `hw detection decrypt` show progression and remove duplicate keys (@doegox) - Changed dynamic cmd_map_init() by static cmd_map initialization (@doegox) - Changed `hf slot list` to add clarity and colors (@doegox) diff --git a/firmware/application/src/rfid/reader/hf/mf1_toolbox.c b/firmware/application/src/rfid/reader/hf/mf1_toolbox.c index 25d21006..80e29cce 100644 --- a/firmware/application/src/rfid/reader/hf/mf1_toolbox.c +++ b/firmware/application/src/rfid/reader/hf/mf1_toolbox.c @@ -9,10 +9,10 @@ #include "nrf_log.h" #include "nrf_log_ctrl.h" #include "nrf_log_default_backends.h" - +#include "bsp_wdt.h" // The default delay of the antenna reset -static uint32_t g_ant_reset_delay = 8; +static uint32_t g_ant_reset_delay = 100; // Label information used for global operations static picc_14a_tag_t m_tag_info; @@ -269,6 +269,8 @@ static uint8_t darkside_select_nonces(picc_14a_tag_t *tag, uint8_t block, uint8_ //Random number collection for (i = 0; i < NT_COUNT; i++) { + bsp_wdt_feed(); + while(NRF_LOG_PROCESS()); //When the antenna is reset, we must make sure // 1. The antenna is powered off for a long time to ensure that the card is completely powered off, otherwise the pseudo -random number generator of the card cannot be reset // 2. Moderate power -off time, don't be too long, it will affect efficiency, and don't be too short. @@ -287,7 +289,7 @@ static uint8_t darkside_select_nonces(picc_14a_tag_t *tag, uint8_t block, uint8_ // Converted to the type of U32 and cache nt_list[i] = bytes_to_num(tag_resp, 4); // The byte array of the conversion response is 10 in NT - // NRF_LOG_INFO("Get nt: %"PRIu32"\r\n", nt_list[i]); + // NRF_LOG_INFO("Get nt: %"PRIu32, nt_list[i]); } // Take the random number @@ -405,9 +407,10 @@ uint8_t darkside_recover_key(uint8_t targetBlk, uint8_t targetTyp, return HF_TAG_OK; } } - // Always collect different NACK under a large cycle do { + bsp_wdt_feed(); + while(NRF_LOG_PROCESS()); // Reset the receiving sign of NACK received_nack = 0; @@ -432,7 +435,7 @@ uint8_t darkside_recover_key(uint8_t targetBlk, uint8_t targetTyp, //The byte array of the conversion response is 10 in NT nt_cur = bytes_to_num(dat_recv, 4); - // NRF_LOG_INFO("Get nt: %"PRIu32"\r\n", nt_cur); + //NRF_LOG_INFO("Get nt: %"PRIu32, nt_cur); //Determine the clock synchronization (fixing NT) if (nt_cur != nt_ori) { @@ -440,7 +443,7 @@ uint8_t darkside_recover_key(uint8_t targetBlk, uint8_t targetTyp, // And the random number we chose was also successfully attacked // In other words, this error has no chance to correct the random number if (++resync_count == ntSyncMax) { - NRF_LOG_INFO("Can't fix nonce.\r\n"); + NRF_LOG_INFO("Can't fix nonce."); *darkside_status = DARKSIDE_CANT_FIX_NT; return HF_TAG_OK; } @@ -451,19 +454,20 @@ uint8_t darkside_recover_key(uint8_t targetBlk, uint8_t targetTyp, // NRF_LOG_INFO("Sync nt -> nt_fix: %"PRIu32", nt_new: %"PRIu32"\r\n", nt_ori, nt_cur); continue; } - //Originally, we only need to send PAR, and use every bit of them as a school test. // But the sending function we implemented only supports one UINT8_T, that is, a byte as a bit // Therefore, we need to replace the communication writing of PM3 into our. // There are not many times here anyway, we directly expand the code for conversion - par_byte[0] = par >> 0 & 0x1; - par_byte[1] = par >> 1 & 0x1; - par_byte[2] = par >> 2 & 0x1; - par_byte[3] = par >> 3 & 0x1; - par_byte[4] = par >> 4 & 0x1; - par_byte[5] = par >> 5 & 0x1; - par_byte[6] = par >> 6 & 0x1; - par_byte[7] = par >> 7 & 0x1; + par_byte[0] = par >> 7 & 0x1; + par_byte[1] = par >> 6 & 0x1; + par_byte[2] = par >> 5 & 0x1; + par_byte[3] = par >> 4 & 0x1; + par_byte[4] = par >> 3 & 0x1; + par_byte[5] = par >> 2 & 0x1; + par_byte[6] = par >> 1 & 0x1; + par_byte[7] = par >> 0 & 0x1; + + //NRF_LOG_INFO("DBG step%i par=%x", nt_diff, par); len = 0; pcd_14a_reader_bits_transfer(mf_nr_ar, 64, par_byte, dat_recv, par_byte, &len, U8ARR_BIT_LEN(dat_recv)); @@ -472,7 +476,7 @@ uint8_t darkside_recover_key(uint8_t targetBlk, uint8_t targetTyp, resync_count = 0; if (len == 4) { - // NRF_LOG_INFO("NACK get: 0x%x\r\n", receivedAnswer[0]); + NRF_LOG_INFO("NACK acquired (%i/8)", nt_diff+1); received_nack = 1; } else if (len == 32) { // did we get lucky and got our dummy key to be valid? @@ -510,8 +514,11 @@ uint8_t darkside_recover_key(uint8_t targetBlk, uint8_t targetTyp, return HF_TAG_OK; } } else { - // Why this? - par = ((par & 0x1F) + 1) | par_low; + par = ((par + 1) & 0x1F) | par_low; + if (par == par_low) { // tried all 32 possible parities without success. Got some NACK but not all 8... + NRF_LOG_INFO("Card sent only %i/8 NACK.", nt_diff); + return DARKSIDE_NO_NAK_SENT; + } } } } while (1); diff --git a/firmware/application/src/rfid/reader/hf/mf1_toolbox.h b/firmware/application/src/rfid/reader/hf/mf1_toolbox.h index 3d8496ee..fe4c0eef 100644 --- a/firmware/application/src/rfid/reader/hf/mf1_toolbox.h +++ b/firmware/application/src/rfid/reader/hf/mf1_toolbox.h @@ -6,6 +6,7 @@ #include #include #include +#include #include "netdata.h" #define SETS_NR 2 // Using several sets of random number probes, at least two can ensure that there are two sets of random number combinations for intersection inquiries. The larger the value, the easier it is to succeed. diff --git a/software/script/chameleon_cli_unit.py b/software/script/chameleon_cli_unit.py index ce031bae..af8dd316 100644 --- a/software/script/chameleon_cli_unit.py +++ b/software/script/chameleon_cli_unit.py @@ -484,7 +484,7 @@ def recover_key(self, block_target, type_target): first_recover = True retry_count = 0 while retry_count < 0xFF: - darkside_resp = self.cmd.mf1_darkside_acquire(block_target, type_target, first_recover, 15) + darkside_resp = self.cmd.mf1_darkside_acquire(block_target, type_target, first_recover, 30) first_recover = False # not first run. if darkside_resp[0] != chameleon_cmd.MifareClassicDarksideStatus.OK: print(f"Darkside error: {chameleon_cmd.MifareClassicDarksideStatus(darkside_resp[0])}") @@ -500,7 +500,7 @@ def recover_key(self, block_target, type_target): else: cmd_recover = f"./darkside {recover_params}" # subprocess.run(cmd_recover, cwd=os.path.abspath("../bin/"), shell=True) - # print(cmd_recover) + # print(f" Executing {cmd_recover}") # start a decrypt process process = self.sub_process(cmd_recover) # wait end @@ -520,8 +520,7 @@ def recover_key(self, block_target, type_target): # auth key for key in key_list: key_bytes = bytearray.fromhex(key) - auth_ret = self.cmd.mf1_auth_one_key_block(block_target, type_target, key_bytes) - if auth_ret.status == chameleon_status.Device.HF_TAG_OK: + if self.cmd.mf1_auth_one_key_block(block_target, type_target, key_bytes): return key return None diff --git a/software/script/chameleon_cmd.py b/software/script/chameleon_cmd.py index 100c4c3b..b3783528 100644 --- a/software/script/chameleon_cmd.py +++ b/software/script/chameleon_cmd.py @@ -512,10 +512,10 @@ def mf1_darkside_acquire(self, block_target, type_target, first_recover: int or :return: """ data = struct.pack('!BBBB', type_target, block_target, first_recover, sync_max) - resp = self.device.send_cmd_sync(DATA_CMD_MF1_DARKSIDE_ACQUIRE, data, timeout=sync_max + 5) + resp = self.device.send_cmd_sync(DATA_CMD_MF1_DARKSIDE_ACQUIRE, data, timeout=sync_max * 10) if resp.status == chameleon_status.Device.HF_TAG_OK: if resp.data[0] == MifareClassicDarksideStatus.OK: - darkside_status, uid, nt1, par, ks1, nr, ar = struct.unpack('!BIIIIII', resp.data) + darkside_status, uid, nt1, par, ks1, nr, ar = struct.unpack('!BIIQQII', resp.data) resp.data = (darkside_status, {'uid': uid, 'nt1': nt1, 'par': par, 'ks1': ks1, 'nr': nr, 'ar': ar}) else: resp.data = (resp.data[0],) From c9261cc6eb417acf08df77d85b9ab48102f27bdc Mon Sep 17 00:00:00 2001 From: Philippe Teuwen Date: Mon, 18 Sep 2023 17:36:52 +0200 Subject: [PATCH 03/38] Darkside: use LEDs for feedback --- CHANGELOG.md | 3 ++- .../src/rfid/reader/hf/mf1_toolbox.c | 19 ++++++++++++++++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 88278320..7affe9ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,8 +3,9 @@ All notable changes to this project will be documented in this file. This project uses the changelog in accordance with [keepchangelog](http://keepachangelog.com/). Please use this to write notable changes, which is not the same as git commit log... ## [unreleased][unreleased] + - Changed Darkside: use LEDs for visual feedback of attack progression (@doegox) - Changed Darkside: longer RF field off for reset and longer CLI timeout (@doegox) - - Fixed Darkside: parity handling bug made it low probability to succeed (@doegox) + - Fixed Darkside: parity byte-to-array bug made it low probability to succeed (@doegox) - Changed `hw detection decrypt` show progression and remove duplicate keys (@doegox) - Changed dynamic cmd_map_init() by static cmd_map initialization (@doegox) - Changed `hf slot list` to add clarity and colors (@doegox) diff --git a/firmware/application/src/rfid/reader/hf/mf1_toolbox.c b/firmware/application/src/rfid/reader/hf/mf1_toolbox.c index 80e29cce..0bec4313 100644 --- a/firmware/application/src/rfid/reader/hf/mf1_toolbox.c +++ b/firmware/application/src/rfid/reader/hf/mf1_toolbox.c @@ -10,6 +10,9 @@ #include "nrf_log_ctrl.h" #include "nrf_log_default_backends.h" #include "bsp_wdt.h" +#include "hw_connect.h" +#include "nrf_gpio.h" +#include "rgb_marquee.h" // The default delay of the antenna reset static uint32_t g_ant_reset_delay = 100; @@ -368,7 +371,7 @@ uint8_t darkside_recover_key(uint8_t targetBlk, uint8_t targetTyp, uint8_t status = 0x00; // This variable is responsible for saving card communication status uint16_t len = 0x00; // This variable is responsible for saving the data of the card in the communication process to respond to the length of the card uint8_t nt_diff = 0x00; // This variable is critical, don't initialize it, because the following is used directly - + bool led_toggle = false; // We need to confirm the use of a certain card first if (pcd_14a_reader_scan_auto(p_tag_info) == HF_TAG_OK) { uid_cur = get_u32_tag_uid(p_tag_info); @@ -378,6 +381,12 @@ uint8_t darkside_recover_key(uint8_t targetBlk, uint8_t targetTyp, // Verification instructions need to add CRC16 crc_14a_append(tag_auth, 2); + rgb_marquee_stop(); + set_slot_light_color(1); + uint32_t *led_pins = hw_get_led_array(); + for (uint8_t i = 0; i < RGB_LIST_NUM; i++) { + nrf_gpio_pin_clear(led_pins[i]); + } // Initialize the static variable if it is the first attack if (firstRecover) { @@ -411,6 +420,13 @@ uint8_t darkside_recover_key(uint8_t targetBlk, uint8_t targetTyp, do { bsp_wdt_feed(); while(NRF_LOG_PROCESS()); + // update LEDs + led_toggle ^= 1; + if (led_toggle) { + nrf_gpio_pin_set(led_pins[nt_diff]); + } else { + nrf_gpio_pin_clear(led_pins[nt_diff]); + } // Reset the receiving sign of NACK received_nack = 0; @@ -488,6 +504,7 @@ uint8_t darkside_recover_key(uint8_t targetBlk, uint8_t targetTyp, // Receive answer. This will be a 4 Bit NACK when the 8 parity bits are OK after decoding if (received_nack) { + nrf_gpio_pin_set(led_pins[nt_diff]); if (nt_diff == 0) { // there is no need to check all parities for other nt_diff. Parity Bits for mf_nr_ar[0..2] won't change par_low = par & 0xE0; From 9497ff7eabb22135e29413c80bc3558c7834277e Mon Sep 17 00:00:00 2001 From: Philippe Teuwen Date: Mon, 18 Sep 2023 23:20:18 +0200 Subject: [PATCH 04/38] Add timeout parameter to hw raw --- docs/protocol.md | 1 - software/script/chameleon_cli_unit.py | 5 +++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/protocol.md b/docs/protocol.md index 16a55b39..ef476e58 100644 --- a/docs/protocol.md +++ b/docs/protocol.md @@ -215,7 +215,6 @@ Notes: * Command: no data * Response: 1 byte, according to `mf1_darkside_status_t` enum * CLI: unused -* **FIXME:** always `CANT_FIX_NT` or watchdog reset on static nonce cards ### 2004: MF1_DARKSIDE_ACQUIRE * Command: 4 bytes: `type_target|block_target|first_recover|sync_max` * Response: 1 byte if Darkside failed, according to `mf1_darkside_status_t` enum, diff --git a/software/script/chameleon_cli_unit.py b/software/script/chameleon_cli_unit.py index af8dd316..d0cf82f3 100644 --- a/software/script/chameleon_cli_unit.py +++ b/software/script/chameleon_cli_unit.py @@ -1471,7 +1471,7 @@ def on_exec(self, args: argparse.Namespace): @hw_settings.command('blepair', 'Check if BLE pairing is enabled, or set the enable switch for BLE pairing.') -class HWRaw(DeviceRequiredUnit): +class HWBlePair(DeviceRequiredUnit): def args_parser(self) -> ArgumentParserNoExit or None: parser = ArgumentParserNoExit() @@ -1520,10 +1520,11 @@ def args_parser(self) -> ArgumentParserNoExit or None: parser = ArgumentParserNoExit() parser.add_argument('-c', '--command', type=int, required=True, help="Command (Int) to send") parser.add_argument('-d', '--data', type=str, help="Data (HEX) to send", default="") + parser.add_argument('-t', '--timeout', type=int, help="Timeout in seconds", default=3) return parser def on_exec(self, args: argparse.Namespace): - response = self.cmd.device.send_cmd_sync(args.command, data=bytes.fromhex(args.data), status=0x0) + response = self.cmd.device.send_cmd_sync(args.command, data=bytes.fromhex(args.data), status=0x0, timeout=args.timeout) print(" - Received:") print(f" Command: {response.cmd}") status_string = f" Status: {response.status:#02x}" From 09eb0d19f450fba9c9f05aac6fdf3ed23c5b14e6 Mon Sep 17 00:00:00 2001 From: Augusto Zanellato Date: Tue, 19 Sep 2023 00:31:22 +0200 Subject: [PATCH 05/38] Automatically send RATS to 14443-4a tags --- firmware/application/src/app_cmd.c | 4 +++- firmware/application/src/app_main.c | 4 ++++ firmware/application/src/app_status.h | 2 +- .../application/src/rfid/reader/hf/rc522.c | 22 +++++++++++++++++++ .../application/src/rfid/reader/hf/rc522.h | 2 ++ software/script/chameleon_cli_unit.py | 1 - software/script/chameleon_status.py | 2 ++ 7 files changed, 34 insertions(+), 3 deletions(-) diff --git a/firmware/application/src/app_cmd.c b/firmware/application/src/app_cmd.c index e5b46165..e441a746 100644 --- a/firmware/application/src/app_cmd.c +++ b/firmware/application/src/app_cmd.c @@ -232,7 +232,9 @@ static data_frame_tx_t *cmd_processor_hf14a_scan(uint16_t cmd, uint16_t status, memcpy(&payload[offset], taginfo.atqa, sizeof(taginfo.atqa)); offset += sizeof(taginfo.atqa); payload[offset++] = taginfo.sak; - payload[offset++] = 0; // TODO: no ATS support yet + payload[offset++] = taginfo.ats_len; + memcpy(&payload[offset], taginfo.ats, taginfo.ats_len); + offset += taginfo.ats_len; return data_frame_make(cmd, HF_TAG_OK, offset, payload); } diff --git a/firmware/application/src/app_main.c b/firmware/application/src/app_main.c index 2d4620b4..fbc33ad3 100644 --- a/firmware/application/src/app_main.c +++ b/firmware/application/src/app_main.c @@ -648,11 +648,15 @@ static void btn_fn_copy_ic_uid(void) { status = pcd_14a_reader_scan_auto(&tag); if (status == HF_TAG_OK) { // copy uid + antres->size = tag.uid_len; memcpy(antres->uid, tag.uid, tag.uid_len); // copy atqa memcpy(antres->atqa, tag.atqa, 2); // copy sak antres->sak[0] = tag.sak; + // copy ats + antres->ats.length = tag.ats_len; + memcpy(antres->ats.data, tag.ats, tag.ats_len); NRF_LOG_INFO("Offline HF uid copied") offline_status_ok(); } else { diff --git a/firmware/application/src/app_status.h b/firmware/application/src/app_status.h index f0848279..574768df 100644 --- a/firmware/application/src/app_status.h +++ b/firmware/application/src/app_status.h @@ -13,7 +13,7 @@ #define HF_ERR_BCC (0x05) // IC card BCC error #define MF_ERR_AUTH (0x06) // MF card verification failed #define HF_ERR_PARITY (0x07) // IC card parity error - +#define HF_ERR_ATS (0x08) // ATS should be present but card NAKed ///////////////////////////////////////////////////////////////////// // lf status diff --git a/firmware/application/src/rfid/reader/hf/rc522.c b/firmware/application/src/rfid/reader/hf/rc522.c index 29d3c27c..aff574d4 100644 --- a/firmware/application/src/rfid/reader/hf/rc522.c +++ b/firmware/application/src/rfid/reader/hf/rc522.c @@ -491,6 +491,7 @@ uint8_t pcd_14a_reader_scan_once(picc_14a_tag_t *tag) { if (tag) { tag->uid_len = 0; memset(tag->uid, 0, 10); + tag->ats_len = 0; } else { return STATUS_PAR_ERR; // Finding cards are not allowed to be transmitted to the label information structure } @@ -578,6 +579,26 @@ uint8_t pcd_14a_reader_scan_once(picc_14a_tag_t *tag) { // Only 1 2 3 Three types, corresponding 4 7 10 Byte card number // Therefore + 1 tag->cascade = cascade_level + 1; + if ((!do_cascade) && (tag->sak & 0x20)) { + // Tag supports 14443-4, sending RATS + uint16_t ats_size; + status = pcd_14a_reader_ats_request(tag->ats, &ats_size, 0xFF*8); + ats_size -= 2; // size returned by pcd_14a_reader_ats_request includes CRC + if (ats_size > 254) { + NRF_LOG_INFO("Invalid ATS > 254!"); + return HF_ERR_ATS; + } + tag->ats_len = ats_size; + // We do not validate ATS here as we want to report ATS as it is without breaking 14a scan + if (tag->ats[0] != ats_size - 1) { + NRF_LOG_INFO("Invalid ATS! First byte doesn't match received length"); + // return HF_ERR_ATS; + } + if (status != HF_TAG_OK) { + NRF_LOG_INFO("Tag SAK claimed to support ATS but tag NAKd RATS"); + // return HF_ERR_ATS; + } + } } return HF_TAG_OK; } @@ -619,6 +640,7 @@ uint8_t pcd_14a_reader_ats_request(uint8_t *pAts, uint16_t *szAts, uint16_t szAt status = pcd_14a_reader_bytes_transfer(PCD_TRANSCEIVE, rats, sizeof(rats), pAts, szAts, szAtsBitMax); if (status != HF_TAG_OK) { + *szAts = 0; NRF_LOG_INFO("Err at ats receive.\n"); return status; } diff --git a/firmware/application/src/rfid/reader/hf/rc522.h b/firmware/application/src/rfid/reader/hf/rc522.h index 2c4d00f2..9546aefc 100644 --- a/firmware/application/src/rfid/reader/hf/rc522.h +++ b/firmware/application/src/rfid/reader/hf/rc522.h @@ -159,6 +159,8 @@ typedef struct { uint8_t cascade; // theAntiCollisionLevelValueIs1Representation 4Byte,2Represents7Byte,3Means10Byte uint8_t sak; // chooseToConfirm uint8_t atqa[2]; // requestResponse + uint8_t ats[0xFF];// 14443-4 answer to select + uint8_t ats_len; // 14443-4 answer to select size } PACKED picc_14a_tag_t; #ifdef __cplusplus diff --git a/software/script/chameleon_cli_unit.py b/software/script/chameleon_cli_unit.py index d0cf82f3..b76453a4 100644 --- a/software/script/chameleon_cli_unit.py +++ b/software/script/chameleon_cli_unit.py @@ -335,7 +335,6 @@ def scan(self, deep=False): print(f"- ATQA : {data_tag['atqa'].hex().upper()}") print(f"- SAK : {data_tag['sak'].hex().upper()}") if len(data_tag['ats']) > 0: - print(data_tag['ats']) print(f"- ATS : {data_tag['ats'].hex().upper()}") if deep: self.sak_info(data_tag) diff --git a/software/script/chameleon_status.py b/software/script/chameleon_status.py index cf820579..da467f50 100644 --- a/software/script/chameleon_status.py +++ b/software/script/chameleon_status.py @@ -30,6 +30,7 @@ class Device(metaclass=MetaDevice): HF_ERR_BCC = 0x05 # IC card BCC error MF_ERR_AUTH = 0x06 # MF card verification failed HF_ERR_PARITY = 0x07 # IC card parity error + HF_ERR_ATS = 0x08 # ATS should be present but card NAKed, or ATS too large # Some operations with low frequency cards succeeded! LF_TAG_OK = 0x40 @@ -57,6 +58,7 @@ class Device(metaclass=MetaDevice): Device.HF_ERR_BCC: "HF tag uid bcc error", Device.MF_ERR_AUTH: "HF tag auth fail", Device.HF_ERR_PARITY: "HF tag data parity error", + Device.HF_ERR_ATS: "HF tag was supposed to send ATS but didn't", Device.LF_TAG_OK: "LF tag operation succeeded", Device.EM410X_TAG_NO_FOUND: "EM410x tag no found", From 6ce61c17208d1b5c530b945fc88aa5df1369a18d Mon Sep 17 00:00:00 2001 From: Philippe Teuwen Date: Tue, 19 Sep 2023 00:38:09 +0200 Subject: [PATCH 06/38] changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7affe9ac..1025dd98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ All notable changes to this project will be documented in this file. This project uses the changelog in accordance with [keepchangelog](http://keepachangelog.com/). Please use this to write notable changes, which is not the same as git commit log... ## [unreleased][unreleased] + - Changed `hf 14a scan`: Automatically send RATS to 14443-4a tags (@augustozanellato) - Changed Darkside: use LEDs for visual feedback of attack progression (@doegox) - Changed Darkside: longer RF field off for reset and longer CLI timeout (@doegox) - Fixed Darkside: parity byte-to-array bug made it low probability to succeed (@doegox) From ff20eb9c689a14a674029f2d72678aac16eeb3ec Mon Sep 17 00:00:00 2001 From: Philippe Teuwen Date: Tue, 19 Sep 2023 01:35:51 +0200 Subject: [PATCH 07/38] simplify anticol+ATS, my fault --- .../application/src/rfid/reader/hf/rc522.c | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/firmware/application/src/rfid/reader/hf/rc522.c b/firmware/application/src/rfid/reader/hf/rc522.c index aff574d4..7df90ec3 100644 --- a/firmware/application/src/rfid/reader/hf/rc522.c +++ b/firmware/application/src/rfid/reader/hf/rc522.c @@ -579,25 +579,25 @@ uint8_t pcd_14a_reader_scan_once(picc_14a_tag_t *tag) { // Only 1 2 3 Three types, corresponding 4 7 10 Byte card number // Therefore + 1 tag->cascade = cascade_level + 1; - if ((!do_cascade) && (tag->sak & 0x20)) { - // Tag supports 14443-4, sending RATS - uint16_t ats_size; - status = pcd_14a_reader_ats_request(tag->ats, &ats_size, 0xFF*8); - ats_size -= 2; // size returned by pcd_14a_reader_ats_request includes CRC - if (ats_size > 254) { - NRF_LOG_INFO("Invalid ATS > 254!"); - return HF_ERR_ATS; - } - tag->ats_len = ats_size; - // We do not validate ATS here as we want to report ATS as it is without breaking 14a scan - if (tag->ats[0] != ats_size - 1) { - NRF_LOG_INFO("Invalid ATS! First byte doesn't match received length"); - // return HF_ERR_ATS; - } - if (status != HF_TAG_OK) { - NRF_LOG_INFO("Tag SAK claimed to support ATS but tag NAKd RATS"); - // return HF_ERR_ATS; - } + } + if (tag->sak & 0x20) { + // Tag supports 14443-4, sending RATS + uint16_t ats_size; + status = pcd_14a_reader_ats_request(tag->ats, &ats_size, 0xFF*8); + ats_size -= 2; // size returned by pcd_14a_reader_ats_request includes CRC + if (ats_size > 254) { + NRF_LOG_INFO("Invalid ATS > 254!"); + return HF_ERR_ATS; + } + tag->ats_len = ats_size; + // We do not validate ATS here as we want to report ATS as it is without breaking 14a scan + if (tag->ats[0] != ats_size - 1) { + NRF_LOG_INFO("Invalid ATS! First byte doesn't match received length"); + // return HF_ERR_ATS; + } + if (status != HF_TAG_OK) { + NRF_LOG_INFO("Tag SAK claimed to support ATS but tag NAKd RATS"); + // return HF_ERR_ATS; } } return HF_TAG_OK; From 42d9f160d9c39fffcaf397db028e4040bf30988c Mon Sep 17 00:00:00 2001 From: Philippe Teuwen Date: Tue, 19 Sep 2023 10:15:42 +0200 Subject: [PATCH 08/38] typo --- docs/technical_whitepaper.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/technical_whitepaper.md b/docs/technical_whitepaper.md index e2adbf5e..308b6d6f 100644 --- a/docs/technical_whitepaper.md +++ b/docs/technical_whitepaper.md @@ -72,7 +72,7 @@ Below we will explain in detail how we exploited the performance of the NRF52840 | Other than <125KHz/ASK/PSK/FSK> | No | No | No | No | Only 125 khz RF, Modulation ASK, FSK and PSK. | | EM410x | ASK | Support | Support | Support | EM4100 is support(AD 64bit) | | T5577 | ASK | Support | Support | Not yet implemented | | -| EM4306 | ASK | Support | Support | Not yet implemented | | +| EM4305 | ASK | Support | Support | Not yet implemented | | | HID Prox | FSK | Support | Support | Not yet implemented | | | Indala | PSK | Support | Support | Not yet implemented | | | FDX-B | ASK | Support | Support | Not yet implemented | | From 32b3e1927914ec392f0b4d140cdfdf0ffda43648 Mon Sep 17 00:00:00 2001 From: dxl <64101226@qq.com> Date: Tue, 19 Sep 2023 18:24:30 +0800 Subject: [PATCH 09/38] Added support for StaticNested(FastDecrypt). --- firmware/application/src/app_cmd.c | 40 +++- firmware/application/src/data_cmd.h | 1 + .../src/rfid/reader/hf/mf1_toolbox.c | 89 +++++++- .../src/rfid/reader/hf/mf1_toolbox.h | 32 ++- software/script/chameleon_cli_unit.py | 66 ++++-- software/script/chameleon_cmd.py | 24 +- software/src/CMakeLists.txt | 3 + software/src/common.c | 20 ++ software/src/common.h | 7 + software/src/darkside.c | 20 +- software/src/nested.c | 207 +----------------- software/src/nested_util.c | 199 +++++++++++++++++ software/src/nested_util.h | 14 ++ software/src/staticnested.c | 77 +++++++ 14 files changed, 529 insertions(+), 270 deletions(-) create mode 100644 software/src/common.c create mode 100644 software/src/common.h create mode 100644 software/src/nested_util.c create mode 100644 software/src/nested_util.h create mode 100644 software/src/staticnested.c diff --git a/firmware/application/src/app_cmd.c b/firmware/application/src/app_cmd.c index e441a746..1fbd36f8 100644 --- a/firmware/application/src/app_cmd.c +++ b/firmware/application/src/app_cmd.c @@ -309,25 +309,27 @@ static data_frame_tx_t *cmd_processor_mf1_detect_nt_dist(uint16_t cmd, uint16_t return data_frame_make(cmd, HF_TAG_OK, sizeof(payload_resp), (uint8_t *)&payload_resp); } +// We have a reusable payload structure. +typedef struct { + uint8_t type_known; + uint8_t block_known; + uint8_t key_known[6]; + uint8_t type_target; + uint8_t block_target; +} PACKED nested_common_payload_t; + static data_frame_tx_t *cmd_processor_mf1_nested_acquire(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - NestedCore_t ncs[SETS_NR]; - typedef struct { - uint8_t type_known; - uint8_t block_known; - uint8_t key_known[6]; - uint8_t type_target; - uint8_t block_target; - } PACKED payload_t; - if (length != sizeof(payload_t)) { + mf1_nested_core_t ncs[SETS_NR]; + if (length != sizeof(nested_common_payload_t)) { return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); } - payload_t *payload = (payload_t *)data; + nested_common_payload_t *payload = (nested_common_payload_t *)data; status = nested_recover_key(bytes_to_num(payload->key_known, 6), payload->block_known, payload->type_known, payload->block_target, payload->type_target, ncs); if (status != HF_TAG_OK) { return data_frame_make(cmd, status, 0, NULL); } - // NestedCore_t is PACKED and comprises only bytes so we can use it directly + // mf1_nested_core_t is PACKED and comprises only bytes so we can use it directly return data_frame_make(cmd, HF_TAG_OK, sizeof(ncs), (uint8_t *)(&ncs)); } @@ -390,6 +392,21 @@ static data_frame_tx_t *cmd_processor_mf1_write_one_block(uint16_t cmd, uint16_t return data_frame_make(cmd, status, 0, NULL); } +static data_frame_tx_t *cmd_processor_mf1_static_nested_acquire(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + mf1_static_nested_core_t sncs; + if (length != sizeof(nested_common_payload_t)) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); + } + + nested_common_payload_t *payload = (nested_common_payload_t *)data; + status = static_nested_recover_key(bytes_to_num(payload->key_known, 6), payload->block_known, payload->type_known, payload->block_target, payload->type_target, &sncs); + if (status != HF_TAG_OK) { + return data_frame_make(cmd, status, 0, NULL); + } + // mf1_static_nested_core_t is PACKED and comprises only bytes so we can use it directly + return data_frame_make(cmd, HF_TAG_OK, sizeof(sncs), (uint8_t *)(&sncs)); +} + static data_frame_tx_t *cmd_processor_em410x_scan(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { uint8_t id_buffer[5] = { 0x00 }; status = PcdScanEM410X(id_buffer); @@ -930,6 +947,7 @@ static cmd_data_map_t m_data_cmd_map[] = { { DATA_CMD_MF1_AUTH_ONE_KEY_BLOCK, before_hf_reader_run, cmd_processor_mf1_auth_one_key_block, after_hf_reader_run }, { DATA_CMD_MF1_READ_ONE_BLOCK, before_hf_reader_run, cmd_processor_mf1_read_one_block, after_hf_reader_run }, { DATA_CMD_MF1_WRITE_ONE_BLOCK, before_hf_reader_run, cmd_processor_mf1_write_one_block, after_hf_reader_run }, + { DATA_CMD_MF1_STATIC_NESTED_ACQUIRE, before_hf_reader_run, cmd_processor_mf1_static_nested_acquire, after_hf_reader_run }, { DATA_CMD_EM410X_SCAN, before_reader_run, cmd_processor_em410x_scan, NULL }, { DATA_CMD_EM410X_WRITE_TO_T55XX, before_reader_run, cmd_processor_em410x_write_to_t55XX, NULL }, diff --git a/firmware/application/src/data_cmd.h b/firmware/application/src/data_cmd.h index 2bdd087c..e04e93c7 100644 --- a/firmware/application/src/data_cmd.h +++ b/firmware/application/src/data_cmd.h @@ -64,6 +64,7 @@ #define DATA_CMD_MF1_AUTH_ONE_KEY_BLOCK (2007) #define DATA_CMD_MF1_READ_ONE_BLOCK (2008) #define DATA_CMD_MF1_WRITE_ONE_BLOCK (2009) +#define DATA_CMD_MF1_STATIC_NESTED_ACQUIRE (2010) // // ****************************************************************** diff --git a/firmware/application/src/rfid/reader/hf/mf1_toolbox.c b/firmware/application/src/rfid/reader/hf/mf1_toolbox.c index 0bec4313..61f1a61a 100644 --- a/firmware/application/src/rfid/reader/hf/mf1_toolbox.c +++ b/firmware/application/src/rfid/reader/hf/mf1_toolbox.c @@ -608,7 +608,7 @@ uint8_t check_tag_response_nt(picc_14a_tag_t *tag, uint32_t *nt) { // Send instructions and get NT return *nt = send_cmd(pcs, AUTH_FIRST, PICC_AUTHENT1A, 0x03, &status, dat_recv, par_recv, U8ARR_BIT_LEN(dat_recv)); if (*nt != 32) { - // dbg_block_printf("No 32 data recv on send_cmd: %d\n", *nt); + // NRF_LOG_INFO("No 32 data recv on send_cmd: %d\n", *nt); return HF_ERR_STAT; } *nt = bytes_to_num(dat_recv, 4); @@ -827,7 +827,7 @@ static uint8_t measure_distance(uint64_t u64Key, uint8_t block, uint8_t type, ui } // After the measurement is completed, store in the buffer distances[index++] = measure_nonces(nt1, nt2); - // dbg_block_printf("dist = %"PRIu32"\n\n", distances[index - 1]); + // NRF_LOG_INFO("dist = %"PRIu32"\n\n", distances[index - 1]); } while (index < DIST_NR); //The final calculation of the distance between the two NTs and spread it directly @@ -847,7 +847,7 @@ static uint8_t measure_distance(uint64_t u64Key, uint8_t block, uint8_t type, ui * @retval : Successfully return hf_tag_ok, verify the unsuccessful return of the non -hf_tag_ok value * */ -static uint8_t nested_recover_core(NestedCore_t *pnc, uint64_t keyKnown, uint8_t blkKnown, uint8_t typKnown, uint8_t targetBlock, uint8_t targetType) { +static uint8_t nested_recover_core(mf1_nested_core_t *pnc, uint64_t keyKnown, uint8_t blkKnown, uint8_t typKnown, uint8_t targetBlock, uint8_t targetType) { struct Crypto1State mpcs = {0, 0}; struct Crypto1State *pcs = &mpcs; uint8_t status; @@ -884,16 +884,16 @@ static uint8_t nested_recover_core(NestedCore_t *pnc, uint64_t keyKnown, uint8_t /** * @brief :NESTED is implemented by default to collect random numbers of the sets_nr group. This function is only responsible for collecting, not responsible for conversion and analysis as KS -* @param :ncs : Nested core structure array, save related communication data * @param :keyKnown : The U64 value of the known secret key of the card * @param :blkKnown :The owner of the known secret key of the card * @param :typKnown : Types of the known secret key of the card, 0x60 (A secret) or 0x61 (B secret) * @param :targetBlock : The target sector that requires a Nested attack * @param :targetType : The target key type requires the Nested attack -* @retval :The attack returns hf_tag_ok, the attack is unsuccessful to return the non -hf_tag_ok value +* @param :ncs : Nested core structure array, save related communication data +* @retval :The attack success return HF_TAG_OK, else return the error code * */ -uint8_t nested_recover_key(uint64_t keyKnown, uint8_t blkKnown, uint8_t typKnown, uint8_t targetBlock, uint8_t targetType, NestedCore_t ncs[SETS_NR]) { +uint8_t nested_recover_key(uint64_t keyKnown, uint8_t blkKnown, uint8_t typKnown, uint8_t targetBlock, uint8_t targetType, mf1_nested_core_t ncs[SETS_NR]) { uint8_t m, res; // all operations must be based on the card res = pcd_14a_reader_scan_auto(p_tag_info); @@ -941,6 +941,83 @@ uint8_t nested_distance_detect(uint8_t block, uint8_t type, uint8_t *key, uint8_ return measure_distance(bytes_to_num(key, 6), block, type, distance); } +/** +* @brief : StaticNested core, used to collect NT. +* This function is only responsible for collection and is not responsible for converting and parsing to KS. +* @param :p_nt1 : NT1, non encrypted. +* @param :p_nt2 : NT2, encrypted. +* @param :keyKnown : U64 value of the known key of the card +* @param :blkKnown : The sector to which the card's known secret key belongs +* @param :typKnown : The known key type of the card, 0x60 (A key) or 0x61 (B key) +* @param :targetBlock : Target sectors that require nested attacks +* @param :targetType : Target key types that require nested attacks +* @param :nestedAgain : StaticNested enhanced vulnerability, which can obtain two sets of encrypted random numbers based on nested verification of known keys +* @retval : Successfully collected and returned to HF_TAG_OK, otherwise an error code will be returned. +* +*/ +uint8_t static_nested_recover_core(uint8_t *p_nt1, uint8_t *p_nt2, uint64_t keyKnown, uint8_t blkKnown, uint8_t typKnown, uint8_t targetBlock, uint8_t targetType, uint8_t nestedAgain) { + struct Crypto1State mpcs = {0, 0}; + struct Crypto1State *pcs = &mpcs; + uint8_t status, len; + uint8_t parity[4] = {0x00}; + uint8_t answer[4] = {0x00}; + uint32_t uid, nt1, nt2; + uid = get_u32_tag_uid(p_tag_info); + pcd_14a_reader_halt_tag(); + if (pcd_14a_reader_scan_auto(p_tag_info) != HF_TAG_OK) { + return HF_TAG_NO; + } + status = authex(pcs, uid, blkKnown, typKnown, keyKnown, AUTH_FIRST, &nt1); + if (status != HF_TAG_OK) { + return MF_ERR_AUTH; + } + if (nestedAgain) { + status = authex(pcs, uid, blkKnown, typKnown, keyKnown, AUTH_NESTED, NULL); + if (status != HF_TAG_OK) { + return MF_ERR_AUTH; + } + } + len = send_cmd(pcs, AUTH_NESTED, targetType, targetBlock, &status, answer, parity, U8ARR_BIT_LEN(answer)); + if (len != 32) { + NRF_LOG_INFO("No 32 data recv on sendcmd: %d\r\n", len); + return HF_ERR_STAT; + } + nt2 = bytes_to_num(answer, 4); + num_to_bytes(nt1, 4, p_nt1); + num_to_bytes(nt2, 4, p_nt2); + return HF_TAG_OK; +} + +/** +* @brief : StaticNested encapsulates and calls the functions implemented by the core to collect 2 sets of random numbers. +* This function is only responsible for collection and is not responsible for converting and parsing to KS. +* @param :keyKnown : U64 value of the known key of the card +* @param :blkKnown : The sector to which the card's known secret key belongs +* @param :typKnown : The known key type of the card, 0x60 (A key) or 0x61 (B key) +* @param :targetBlock : Target sectors that require nested attacks +* @param :targetType : Target key type that require nested attacks +* @param :sncs : StaticNested Decrypting Core Structure Array +* @retval : Successfully collected and returned to HF_TAG_OK, otherwise an error code will be returned. +* +*/ +uint8_t static_nested_recover_key(uint64_t keyKnown, uint8_t blkKnown, uint8_t typKnown, uint8_t targetBlock, uint8_t targetType, mf1_static_nested_core_t* sncs) { + uint8_t res; + res = pcd_14a_reader_scan_auto(p_tag_info); + if (res!= HF_TAG_OK) { + return res; + } + get_4byte_tag_uid(p_tag_info, sncs->uid); + res = static_nested_recover_core(sncs->core[0].nt1, sncs->core[0].nt2, keyKnown, blkKnown, typKnown, targetBlock, targetType, false); + if (res != HF_TAG_OK) { + return res; + } + res = static_nested_recover_core(sncs->core[1].nt1, sncs->core[1].nt2, keyKnown, blkKnown, typKnown, targetBlock, targetType, true); + if (res != HF_TAG_OK) { + return res; + } + return HF_TAG_OK; +} + /** * @brief : Use the RC522 M1 algorithm module to verify the key * @retval : validationResults diff --git a/firmware/application/src/rfid/reader/hf/mf1_toolbox.h b/firmware/application/src/rfid/reader/hf/mf1_toolbox.h index fe4c0eef..40b35ac9 100644 --- a/firmware/application/src/rfid/reader/hf/mf1_toolbox.h +++ b/firmware/application/src/rfid/reader/hf/mf1_toolbox.h @@ -29,14 +29,22 @@ typedef struct { //Answer the random number parameters required for N uint8_t nt1[4]; //Unblocked explicitly random number uint8_t nt2[4]; //Random number of nested verification encryption uint8_t par; //The puppet test of the communication process of nested verification encryption, only the "low 3 digits', that is, the right 3 -} NestedCore_t; +} mf1_nested_core_t; + +typedef struct { + uint8_t uid[4]; + struct { + uint8_t nt1[4]; + uint8_t nt2[4]; + } core[2]; +} mf1_static_nested_core_t; typedef enum { DARKSIDE_OK = 0u, // normal process DARKSIDE_CANT_FIX_NT = 1u, // the random number cannot be fixed, this situation may appear on some UID card DARKSIDE_LUCKY_AUTH_OK = 2u, // the direct authentification is successful, maybe the key is just the default one DARKSIDE_NO_NAK_SENT = 3u, // the card does not respond to NACK, it may be a card that fixes Nack logic vulnerabilities - DARKSIDE_TAG_CHANGED = 4u, // card swap while running DARKSIDE + DARKSIDE_TAG_CHANGED = 4u, // card change while running DARKSIDE } mf1_darkside_status_t; // this struct is also used in the fw/cli protocol, therefore PACKED @@ -63,6 +71,7 @@ uint8_t darkside_recover_key( DarksideCore_t *dc, mf1_darkside_status_t *darkside_status ); + uint8_t nested_distance_detect( uint8_t block, uint8_t type, @@ -70,14 +79,17 @@ uint8_t nested_distance_detect( uint8_t *uid, uint32_t *distance ); -uint8_t nested_recover_key( - uint64_t keyKnown, - uint8_t blkKnown, - uint8_t typKnown, - uint8_t targetBlock, - uint8_t targetType, - NestedCore_t ncs[SETS_NR] -); + +#define NESTED_CORE_PARAM_DEF \ + uint64_t keyKnown, \ + uint8_t blkKnown, \ + uint8_t typKnown, \ + uint8_t targetBlock, \ + uint8_t targetType \ + +uint8_t nested_recover_key(NESTED_CORE_PARAM_DEF, mf1_nested_core_t ncs[SETS_NR]); +uint8_t static_nested_recover_key(NESTED_CORE_PARAM_DEF, mf1_static_nested_core_t* sncs); + uint8_t check_darkside_support(mf1_darkside_status_t *darkside_status); uint8_t check_prng_type(mf1_prng_type_t *type); uint8_t check_std_mifare_nt_support(bool *support); diff --git a/software/script/chameleon_cli_unit.py b/software/script/chameleon_cli_unit.py index b76453a4..ea592e72 100644 --- a/software/script/chameleon_cli_unit.py +++ b/software/script/chameleon_cli_unit.py @@ -8,6 +8,7 @@ import sys import time import serial.tools.list_ports +import threading from platform import uname import chameleon_com @@ -82,9 +83,17 @@ def on_exec(self, args: argparse.Namespace): def sub_process(cmd, cwd=os.path.abspath("bin/")): class ShadowProcess: def __init__(self): + self.output = "" self.time_start = timeit.default_timer() self._process = subprocess.Popen(cmd, cwd=cwd, shell=True, stderr=subprocess.PIPE, stdout=subprocess.PIPE) + threading.Thread(target=self.thread_read_output).start() + + def thread_read_output(self): + while self._process.poll() is None: + data = self._process.stdout.read(1024) + if len(data) > 0: + self.output += data.decode(encoding="utf-8") def get_time_distance(self, ms=True): if ms: @@ -101,15 +110,8 @@ def is_timeout(self, timeout_ms): return True return False - def get_output_sync(self, encoding='utf-8'): - buffer = bytearray() - while True: - data = self._process.stdout.read(1024) - if len(data) > 0: - buffer.extend(data) - else: - break - return buffer.decode(encoding) + def get_output_sync(self): + return self.output def get_ret_code(self): return self._process.poll() @@ -380,6 +382,14 @@ def args_parser(self) -> ArgumentParserNoExit or None: help="The type of the target block to recover") # hf mf nested -o --block-known 0 --type-known A --key FFFFFFFFFFFF --block-target 4 --type-target A return parser + + def from_nt_level_code_to_str(self, nt_level): + if nt_level == 0: + return 'StaticNested' + if nt_level == 1: + return 'Nested' + if nt_level == 2: + return 'HardNested' def recover_a_key(self, block_known, type_known, key_known, block_target, type_target) -> str or None: """ @@ -391,17 +401,35 @@ def recover_a_key(self, block_known, type_known, key_known, block_target, type_t :param type_target: :return: """ + # check nt level, we can run static or nested auto... + nt_level = self.cmd.mf1_detect_prng() + print(f" - NT vulnerable: {CY}{ self.from_nt_level_code_to_str(nt_level) }{C0}") + if nt_level == 2: + print(" [!] HardNested has not been implemented yet.") + return None + # acquire - dist_obj = self.cmd.mf1_detect_nt_dist(block_known, type_known, key_known) - nt_obj = self.cmd.mf1_nested_acquire(block_known, type_known, key_known, block_target, type_target) - # create cmd - cmd_param = f"{dist_obj['uid']} {dist_obj['dist']}" - for nt_item in nt_obj: - cmd_param += f" {nt_item['nt']} {nt_item['nt_enc']} {nt_item['par']}" + if nt_level == 0: # It's a staticnested tag? + nt_uid_obj = self.cmd.mf1_static_nested_acquire(block_known, type_known, key_known, block_target, type_target) + cmd_param = f"{nt_uid_obj['uid']} {str(type_target)}" + for nt_item in nt_uid_obj['nts']: + cmd_param += f" {nt_item['nt']} {nt_item['nt_enc']}" + decryptor_name = "staticnested" + else: + dist_obj = self.cmd.mf1_detect_nt_dist(block_known, type_known, key_known) + nt_obj = self.cmd.mf1_nested_acquire(block_known, type_known, key_known, block_target, type_target) + # create cmd + cmd_param = f"{dist_obj['uid']} {dist_obj['dist']}" + for nt_item in nt_obj: + cmd_param += f" {nt_item['nt']} {nt_item['nt_enc']} {nt_item['par']}" + decryptor_name = "nested" + + # Cross-platform compatibility if sys.platform == "win32": - cmd_recover = f"nested.exe {cmd_param}" + cmd_recover = f"{decryptor_name}.exe {cmd_param}" else: - cmd_recover = f"./nested {cmd_param}" + cmd_recover = f"./{decryptor_name} {cmd_param}" + print(f" Executing {cmd_recover}") # start a decrypt process process = self.sub_process(cmd_recover) @@ -489,6 +517,10 @@ def recover_key(self, block_target, type_target): print(f"Darkside error: {chameleon_cmd.MifareClassicDarksideStatus(darkside_resp[0])}") break darkside_obj = darkside_resp[1] + + if darkside_obj['par'] != 0: # NXP tag workaround. + self.darkside_list.clear() + self.darkside_list.append(darkside_obj) recover_params = f"{darkside_obj['uid']}" for darkside_item in self.darkside_list: diff --git a/software/script/chameleon_cmd.py b/software/script/chameleon_cmd.py index b3783528..2f8ef128 100644 --- a/software/script/chameleon_cmd.py +++ b/software/script/chameleon_cmd.py @@ -69,6 +69,7 @@ DATA_CMD_MF1_AUTH_ONE_KEY_BLOCK = 2007 DATA_CMD_MF1_READ_ONE_BLOCK = 2008 DATA_CMD_MF1_WRITE_ONE_BLOCK = 2009 +DATA_CMD_MF1_STATIC_NESTED_ACQUIRE = 2010 DATA_CMD_EM410X_SCAN = 3000 DATA_CMD_EM410X_WRITE_TO_T55XX = 3001 @@ -445,7 +446,7 @@ def hf14a_scan(self): @expect_response(chameleon_status.Device.HF_TAG_OK) def mf1_detect_support(self): """ - Detect whether it is mifare classic label + Detect whether it is mifare classic tag :return: """ resp = self.device.send_cmd_sync(DATA_CMD_MF1_DETECT_SUPPORT) @@ -501,6 +502,7 @@ def mf1_nested_acquire(self, block_known, type_known, key_known, block_target, t for nt, nt_enc, par in struct.iter_unpack('!IIB', resp.data)] return resp + @expect_response(chameleon_status.Device.HF_TAG_OK) def mf1_darkside_acquire(self, block_target, type_target, first_recover: int or bool, sync_max): """ @@ -561,6 +563,26 @@ def mf1_write_one_block(self, block, type_value, key, block_data): resp = self.device.send_cmd_sync(DATA_CMD_MF1_WRITE_ONE_BLOCK, data) resp.data = resp.status == chameleon_status.Device.HF_TAG_OK return resp + + @expect_response(chameleon_status.Device.HF_TAG_OK) + def mf1_static_nested_acquire(self, block_known, type_known, key_known, block_target, type_target): + """ + Collect the key NT parameters needed for StaticNested decryption + :return: + """ + data = struct.pack('!BB6sBB', type_known, block_known, key_known, type_target, block_target) + resp = self.device.send_cmd_sync(DATA_CMD_MF1_STATIC_NESTED_ACQUIRE, data) + if resp.status == chameleon_status.Device.HF_TAG_OK: + resp.data = { + 'uid': struct.unpack('!I', resp.data[0:4])[0], + 'nts': [ + { + 'nt': nt, + 'nt_enc': nt_enc + } for nt, nt_enc in struct.iter_unpack('!II', resp.data[4:]) + ] + } + return resp @expect_response(chameleon_status.Device.LF_TAG_OK) def em410x_scan(self): diff --git a/software/src/CMakeLists.txt b/software/src/CMakeLists.txt index 5444f767..7c4d0665 100644 --- a/software/src/CMakeLists.txt +++ b/software/src/CMakeLists.txt @@ -6,10 +6,12 @@ set(EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../bin) set(SRC_DIR ./) set(COMMON_FILES + ${SRC_DIR}/common.c ${SRC_DIR}/crapto1.c ${SRC_DIR}/crypto1.c ${SRC_DIR}/bucketsort.c ${SRC_DIR}/mfkey.c + ${SRC_DIR}/nested_util.c ${SRC_DIR}/parity.c) include_directories( @@ -19,6 +21,7 @@ include_directories( # tools add_executable(nested ${COMMON_FILES} nested.c) +add_executable(staticnested ${COMMON_FILES} staticnested.c) add_executable(darkside ${COMMON_FILES} darkside.c) add_executable(mfkey32 ${COMMON_FILES} mfkey32.c) add_executable(mfkey32v2 ${COMMON_FILES} mfkey32v2.c) diff --git a/software/src/common.c b/software/src/common.c new file mode 100644 index 00000000..e405d1ca --- /dev/null +++ b/software/src/common.c @@ -0,0 +1,20 @@ +#include + + +uint64_t atoui(const char* str) { + + uint64_t result = 0; + for (int i = 0; str[i] != '\0'; ++i) { + if (str[i] >= '0' && str[i] <= '9') { + result = result * 10 + str[i] - '0'; + } + } + return result; +} + +void num_to_bytes(uint64_t n, uint32_t len, uint8_t* dest) { + while (len--) { + dest[len] = (uint8_t)n; + n >>= 8; + } +} diff --git a/software/src/common.h b/software/src/common.h new file mode 100644 index 00000000..278df6a6 --- /dev/null +++ b/software/src/common.h @@ -0,0 +1,7 @@ +#ifndef NESTED_H__ +#define NESTED_H__ + +uint64_t atoui(const char* str); +void num_to_bytes(uint64_t n, uint32_t len, uint8_t* dest); + +#endif \ No newline at end of file diff --git a/software/src/darkside.c b/software/src/darkside.c index 99105b4d..5bc72b92 100644 --- a/software/src/darkside.c +++ b/software/src/darkside.c @@ -7,6 +7,7 @@ #include "parity.h" #include "crapto1.h" #include "mfkey.h" +#include "common.h" typedef struct { uint32_t nt; @@ -17,25 +18,6 @@ typedef struct { uint64_t ks_list; } DarksideParam; -// Convert string to U32 type -uint64_t atoui(const char *str) { - - uint64_t result = 0; - for (int i = 0; str[i] != '\0'; ++i) { - if (str[i] >= '0' && str[i] <= '9') { - result = result * 10 + str[i] - '0'; - } - } - return result; -} - -void num_to_bytes(uint64_t n, uint32_t len, uint8_t *dest) { - while (len--) { - dest[len] = (uint8_t)n; - n >>= 8; - } -} - int main(int argc, char *argv[]) { if (((argc - 2) % 5) != 0) { diff --git a/software/src/nested.c b/software/src/nested.c index 2e8be0a1..33b5b2a4 100644 --- a/software/src/nested.c +++ b/software/src/nested.c @@ -1,214 +1,9 @@ #include #include #include -#include #include -#include -#include "crapto1.h" -#include "parity.h" -#if WIN32 -#include "windows.h" -#else -#include "unistd.h" -#endif - - -#define MEM_CHUNK 10000 -#define TRY_KEYS 50 - - -typedef struct { - uint64_t key; - int count; -} countKeys; - -typedef struct { - uint32_t ntp; - uint32_t ks1; -} NtpKs1; - -typedef struct { - NtpKs1 *pNK; - uint32_t authuid; - - uint64_t *keys; - uint32_t keyCount; - - uint32_t startPos; - uint32_t endPos; -} RecPar; - - -int compar_int(const void *a, const void *b) { - return (*(uint64_t *)b - * (uint64_t *)a); -} - -// Compare countKeys structure -int compar_special_int(const void *a, const void *b) { - return (((countKeys *)b)->count - ((countKeys *)a)->count); -} - -// keys qsort and unique. -countKeys *uniqsort(uint64_t *possibleKeys, uint32_t size) { - unsigned int i, j = 0; - int count = 0; - countKeys *our_counts; - - qsort(possibleKeys, size, sizeof(uint64_t), compar_int); - - our_counts = calloc(size, sizeof(countKeys)); - if (our_counts == NULL) { - printf("Memory allocation error for our_counts"); - exit(EXIT_FAILURE); - } - - for (i = 0; i < size; i++) { - if (possibleKeys[i + 1] == possibleKeys[i]) { - count++; - } else { - our_counts[j].key = possibleKeys[i]; - our_counts[j].count = count; - j++; - count = 0; - } - } - qsort(our_counts, j, sizeof(countKeys), compar_special_int); - return (our_counts); -} - -uint32_t atoui(const char *str) { - - uint32_t result = 0; - for (int i = 0; str[i] != '\0'; ++i) { - if (str[i] >= '0' && str[i] <= '9') { - result = result * 10 + str[i] - '0'; - } - } - return result; -} - -// nested decrypt -static void nested_revover(RecPar *rp) { - struct Crypto1State *revstate, * revstate_start = NULL; - uint64_t lfsr = 0; - uint32_t i, kcount = 0; - - rp->keyCount = 0; - rp->keys = NULL; - - for (i = rp->startPos; i < rp->endPos; i++) { - uint32_t nt_probe = rp->pNK[i].ntp; - uint32_t ks1 = rp->pNK[i].ks1; - // And finally recover the first 32 bits of the key - revstate = lfsr_recovery32(ks1, nt_probe ^ rp->authuid); - if (revstate_start == NULL) { - revstate_start = revstate; - } - while ((revstate->odd != 0x0) || (revstate->even != 0x0)) { - lfsr_rollback_word(revstate, nt_probe ^ rp->authuid, 0); - crypto1_get_lfsr(revstate, &lfsr); - // Allocate a new space for keys - if (((kcount % MEM_CHUNK) == 0) || (kcount >= rp->keyCount)) { - rp->keyCount += MEM_CHUNK; - // printf("New chunk by %d, sizeof %lu\n", kcount, key_count * sizeof(uint64_t)); - void *tmp = realloc(rp->keys, rp->keyCount * sizeof(uint64_t)); - if (tmp == NULL) { - printf("Memory allocation error for pk->possibleKeys"); - // exit(EXIT_FAILURE); - rp->keyCount = 0; - return; - } - rp->keys = (uint64_t *)tmp; - } - rp->keys[kcount] = lfsr; - kcount++; - revstate++; - } - free(revstate_start); - revstate_start = NULL; - } - // Truncate - if (kcount != 0) { - rp->keyCount = --kcount; - void *tmp = (uint64_t *)realloc(rp->keys, rp->keyCount * sizeof(uint64_t)); - if (tmp == NULL) { - printf("Memory allocation error for pk->possibleKeys"); - // exit(EXIT_FAILURE); - rp->keyCount = 0; - return; - } - rp->keys = tmp; - return; - } - rp->keyCount = 0; - return; -} - -uint64_t *nested(NtpKs1 *pNK, uint32_t sizePNK, uint32_t authuid, uint32_t *keyCount) { - *keyCount = 0; - uint32_t i; - - RecPar *pRPs = malloc(sizeof(RecPar)); - if (pRPs == NULL) { - return NULL; - } - - pRPs->pNK = pNK; - pRPs->authuid = authuid; - pRPs->startPos = 0; - pRPs->endPos = sizePNK; - - // start recover - nested_revover(pRPs); - *keyCount = pRPs->keyCount; - - uint64_t *keys = NULL; - if (*keyCount != 0) { - keys = malloc(*keyCount * sizeof(uint64_t)); - if (keys != NULL) { - memcpy(keys, pRPs->keys, pRPs->keyCount * sizeof(uint64_t)); - free(pRPs->keys); - } - } - free(pRPs); - - countKeys *ck = uniqsort(keys, *keyCount); - free(keys); - keys = (uint64_t *)NULL; - *keyCount = 0; - - if (ck != NULL) { - for (i = 0; i < TRY_KEYS; i++) { - // We don't known this key, try to break it - // This key can be found here two or more times - if (ck[i].count > 0) { - *keyCount += 1; - void *tmp = realloc(keys, sizeof(uint64_t) * (*keyCount)); - if (tmp != NULL) { - keys = tmp; - keys[*keyCount - 1] = ck[i].key; - } else { - printf("Cannot allocate memory for keys on merge."); - free(keys); - break; - } - } - } - } else { - printf("Cannot allocate memory for ck on uniqsort."); - } - return keys; -} - -// Return 1 if the nonce is invalid else return 0 -static uint8_t valid_nonce(uint32_t Nt, uint32_t NtEnc, uint32_t Ks1, uint8_t *parity) { - return ( - (oddparity8((Nt >> 24) & 0xFF) == ((parity[0]) ^ oddparity8((NtEnc >> 24) & 0xFF) ^ BIT(Ks1, 16))) && \ - (oddparity8((Nt >> 16) & 0xFF) == ((parity[1]) ^ oddparity8((NtEnc >> 16) & 0xFF) ^ BIT(Ks1, 8))) && \ - (oddparity8((Nt >> 8) & 0xFF) == ((parity[2]) ^ oddparity8((NtEnc >> 8) & 0xFF) ^ BIT(Ks1, 0))) - ) ? 1 : 0; -} +#include "nested_util.h" int main(int argc, char *const argv[]) { NtpKs1 *pNK = NULL; diff --git a/software/src/nested_util.c b/software/src/nested_util.c new file mode 100644 index 00000000..7d214238 --- /dev/null +++ b/software/src/nested_util.c @@ -0,0 +1,199 @@ +#include +#include +#include +#include +#include +#include +#include "parity.h" + +#if WIN32 +#include "windows.h" +#else +#include "unistd.h" +#endif + +#include "nested_util.h" + + +#define MEM_CHUNK 10000 +#define TRY_KEYS 50 + + +typedef struct { + uint64_t key; + int count; +} countKeys; + +typedef struct { + NtpKs1* pNK; + uint32_t authuid; + + uint64_t* keys; + uint32_t keyCount; + + uint32_t startPos; + uint32_t endPos; +} RecPar; + + +int compar_int(const void* a, const void* b) { + return (*(uint64_t*)b - *(uint64_t*)a); +} + +// Compare countKeys structure +int compar_special_int(const void* a, const void* b) { + return (((countKeys*)b)->count - ((countKeys*)a)->count); +} + +// keys qsort and unique. +countKeys* uniqsort(uint64_t* possibleKeys, uint32_t size) { + unsigned int i, j = 0; + int count = 0; + countKeys* our_counts; + + qsort(possibleKeys, size, sizeof(uint64_t), compar_int); + + our_counts = calloc(size, sizeof(countKeys)); + if (our_counts == NULL) { + printf("Memory allocation error for our_counts"); + exit(EXIT_FAILURE); + } + + for (i = 0; i < size; i++) { + if (possibleKeys[i + 1] == possibleKeys[i]) { + count++; + } + else { + our_counts[j].key = possibleKeys[i]; + our_counts[j].count = count; + j++; + count = 0; + } + } + qsort(our_counts, j, sizeof(countKeys), compar_special_int); + return (our_counts); +} + +// nested decrypt +static void nested_revover(RecPar* rp) { + struct Crypto1State* revstate, * revstate_start = NULL; + uint64_t lfsr = 0; + uint32_t i, kcount = 0; + + rp->keyCount = 0; + rp->keys = NULL; + + for (i = rp->startPos; i < rp->endPos; i++) { + uint32_t nt_probe = rp->pNK[i].ntp; + uint32_t ks1 = rp->pNK[i].ks1; + // And finally recover the first 32 bits of the key + revstate = lfsr_recovery32(ks1, nt_probe ^ rp->authuid); + if (revstate_start == NULL) { + revstate_start = revstate; + } + while ((revstate->odd != 0x0) || (revstate->even != 0x0)) { + lfsr_rollback_word(revstate, nt_probe ^ rp->authuid, 0); + crypto1_get_lfsr(revstate, &lfsr); + // Allocate a new space for keys + if (((kcount % MEM_CHUNK) == 0) || (kcount >= rp->keyCount)) { + rp->keyCount += MEM_CHUNK; + // printf("New chunk by %d, sizeof %lu\n", kcount, key_count * sizeof(uint64_t)); + void* tmp = realloc(rp->keys, rp->keyCount * sizeof(uint64_t)); + if (tmp == NULL) { + printf("Memory allocation error for pk->possibleKeys"); + // exit(EXIT_FAILURE); + rp->keyCount = 0; + return; + } + rp->keys = (uint64_t*)tmp; + } + rp->keys[kcount] = lfsr; + kcount++; + revstate++; + } + free(revstate_start); + revstate_start = NULL; + } + // Truncate + if (kcount != 0) { + rp->keyCount = --kcount; + void* tmp = (uint64_t*)realloc(rp->keys, rp->keyCount * sizeof(uint64_t)); + if (tmp == NULL) { + printf("Memory allocation error for pk->possibleKeys"); + // exit(EXIT_FAILURE); + rp->keyCount = 0; + return; + } + rp->keys = tmp; + return; + } + rp->keyCount = 0; + return; +} + +uint64_t* nested(NtpKs1* pNK, uint32_t sizePNK, uint32_t authuid, uint32_t* keyCount) { + *keyCount = 0; + uint32_t i; + + RecPar* pRPs = malloc(sizeof(RecPar)); + if (pRPs == NULL) { + return NULL; + } + + pRPs->pNK = pNK; + pRPs->authuid = authuid; + pRPs->startPos = 0; + pRPs->endPos = sizePNK; + + // start recover + nested_revover(pRPs); + *keyCount = pRPs->keyCount; + + uint64_t* keys = NULL; + if (*keyCount != 0) { + keys = malloc(*keyCount * sizeof(uint64_t)); + if (keys != NULL) { + memcpy(keys, pRPs->keys, pRPs->keyCount * sizeof(uint64_t)); + free(pRPs->keys); + } + } + free(pRPs); + + countKeys* ck = uniqsort(keys, *keyCount); + free(keys); + keys = (uint64_t*)NULL; + *keyCount = 0; + + if (ck != NULL) { + for (i = 0; i < TRY_KEYS; i++) { + // We don't known this key, try to break it + // This key can be found here two or more times + if (ck[i].count > 0) { + *keyCount += 1; + void* tmp = realloc(keys, sizeof(uint64_t) * (*keyCount)); + if (tmp != NULL) { + keys = tmp; + keys[*keyCount - 1] = ck[i].key; + } + else { + printf("Cannot allocate memory for keys on merge."); + free(keys); + break; + } + } + } + } + else { + printf("Cannot allocate memory for ck on uniqsort."); + } + return keys; +} + +// Return 1 if the nonce is invalid else return 0 +uint8_t valid_nonce(uint32_t Nt, uint32_t NtEnc, uint32_t Ks1, uint8_t* parity) { + return ( + (oddparity8((Nt >> 24) & 0xFF) == ((parity[0]) ^ oddparity8((NtEnc >> 24) & 0xFF) ^ BIT(Ks1, 16))) && \ + (oddparity8((Nt >> 16) & 0xFF) == ((parity[1]) ^ oddparity8((NtEnc >> 16) & 0xFF) ^ BIT(Ks1, 8))) && \ + (oddparity8((Nt >> 8) & 0xFF) == ((parity[2]) ^ oddparity8((NtEnc >> 8) & 0xFF) ^ BIT(Ks1, 0))) + ) ? 1 : 0; +} diff --git a/software/src/nested_util.h b/software/src/nested_util.h new file mode 100644 index 00000000..12699ac4 --- /dev/null +++ b/software/src/nested_util.h @@ -0,0 +1,14 @@ +#ifndef NESTED_H__ +#define NESTED_H__ + +#include "crapto1.h" + +typedef struct { + uint32_t ntp; + uint32_t ks1; +} NtpKs1; + +uint8_t valid_nonce(uint32_t Nt, uint32_t NtEnc, uint32_t Ks1, uint8_t* parity); +uint64_t* nested(NtpKs1* pNK, uint32_t sizePNK, uint32_t authuid, uint32_t* keyCount); + +#endif \ No newline at end of file diff --git a/software/src/staticnested.c b/software/src/staticnested.c new file mode 100644 index 00000000..73ed002d --- /dev/null +++ b/software/src/staticnested.c @@ -0,0 +1,77 @@ +#include +#include +#include +#include + +#include "nested_util.h" + +int main(int argc, char *const argv[]) { + NtpKs1 *pNK = NULL; + uint32_t i, j, m; + uint32_t nt1, nt2, nttest, ks1, dist; + + uint32_t authuid = atoui(argv[1]); // uid + uint8_t type = (uint8_t)atoui(argv[2]); // target key type + + // process all args. + bool check_st_level_at_sirst_run = false; + for (i = 3, j = 0; i < argc; i += 2) { + // nt + par + nt1 = atoui(argv[i]); + nt2 = atoui(argv[i + 1]); + + // Which generation of ST tags is detected. + if (!check_st_level_at_sirst_run) { + if (nt1 == 0x01200145) { + // ·¢ÏÖÒ»´úÎÞ©¶´£¬´Ë±êÇ©È«¿¨¿ÉÓÃĬÈÏ160µÄ²ÎÊý½øÐнâÃÜ£¡ + dist = 160; // st gen1 + } + else if (nt1 == 0x009080A2) { // st gen2 + // ·¢ÏÖÎÞ©¶´¶þ´ú£¬ÎÒÃÇÈç¹ûÈ·ÈÏÄ¿Ç°ÐèÒª¹¥»÷µÄʱBÃÜÔ¿£¬ÄÇô¾ÍÐèÒª¸ü»»¹¥»÷²ÎÊý + if (type == 0x61) { + dist = 161; + } + else if (type == 0x60) { + dist = 160; + } + else { + // can't to here!!! + goto error; + } + } + else { + // can't to here!!! + goto error; + } + check_st_level_at_sirst_run = true; + } + + nttest = prng_successor(nt1, dist); + ks1 = nt2 ^ nttest; + ++j; + dist += 160; + + void* tmp = realloc(pNK, sizeof(NtpKs1) * j); + if (tmp == NULL) { + goto error; + } + + pNK = tmp; + pNK[j - 1].ntp = nttest; + pNK[j - 1].ks1 = ks1; + } + uint32_t keyCount = 0; + uint64_t *keys = nested(pNK, j, authuid, &keyCount); + + if (keyCount > 0) { + for (i = 0; i < keyCount; i++) { + printf("Key %d... %" PRIx64 " \r\n", i + 1, keys[i]); + fflush(stdout); + } + } + fflush(stdout); + free(keys); + exit(EXIT_SUCCESS); +error: + exit(EXIT_FAILURE); +} From f0ddd071083c2b63bff79160af9572bb47780261 Mon Sep 17 00:00:00 2001 From: dxl <64101226@qq.com> Date: Tue, 19 Sep 2023 18:25:01 +0800 Subject: [PATCH 10/38] A bug has been marked and is waiting to be fixed. --- firmware/application/src/rfid/reader/hf/rc522.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/firmware/application/src/rfid/reader/hf/rc522.c b/firmware/application/src/rfid/reader/hf/rc522.c index 7df90ec3..0aec993d 100644 --- a/firmware/application/src/rfid/reader/hf/rc522.c +++ b/firmware/application/src/rfid/reader/hf/rc522.c @@ -599,6 +599,10 @@ uint8_t pcd_14a_reader_scan_once(picc_14a_tag_t *tag) { NRF_LOG_INFO("Tag SAK claimed to support ATS but tag NAKd RATS"); // return HF_ERR_ATS; } + /* + * FIXME: If there is an issue here, it will cause the label to lose its selected state. + * It is necessary to reselect the card after the issue occurs here. + */ } return HF_TAG_OK; } From 624e96aee94a31bcf21d041da3def68259740286 Mon Sep 17 00:00:00 2001 From: Philippe Teuwen Date: Tue, 19 Sep 2023 13:18:02 +0200 Subject: [PATCH 11/38] fix compilation --- software/src/common.h | 6 +++--- software/src/nested.c | 2 +- software/src/staticnested.c | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/software/src/common.h b/software/src/common.h index 278df6a6..8fac65be 100644 --- a/software/src/common.h +++ b/software/src/common.h @@ -1,7 +1,7 @@ -#ifndef NESTED_H__ -#define NESTED_H__ +#ifndef COMMON_H__ +#define COMMON_H__ uint64_t atoui(const char* str); void num_to_bytes(uint64_t n, uint32_t len, uint8_t* dest); -#endif \ No newline at end of file +#endif diff --git a/software/src/nested.c b/software/src/nested.c index 33b5b2a4..fde471bb 100644 --- a/software/src/nested.c +++ b/software/src/nested.c @@ -2,7 +2,7 @@ #include #include #include - +#include "common.h" #include "nested_util.h" int main(int argc, char *const argv[]) { diff --git a/software/src/staticnested.c b/software/src/staticnested.c index 73ed002d..d1ba0bda 100644 --- a/software/src/staticnested.c +++ b/software/src/staticnested.c @@ -2,7 +2,7 @@ #include #include #include - +#include "common.h" #include "nested_util.h" int main(int argc, char *const argv[]) { From 5a9d49ddb421e400760bfff9c24bca1dacc82a5a Mon Sep 17 00:00:00 2001 From: Philippe Teuwen Date: Tue, 19 Sep 2023 16:03:17 +0200 Subject: [PATCH 12/38] Remove unused MF1_DETECT_DARKSIDE, reallocate 2003 to MF1_STATIC_NESTED_ACQUIRE MF1_DETECT_DARKSIDE was actually runnign the full attack, as there is no quick way to detect Darkside So if you want to check for Darkside, just run directly MF1_DARKSIDE_ACQUIRE. --- docs/protocol.md | 8 ++++---- firmware/application/src/app_cmd.c | 13 +------------ firmware/application/src/data_cmd.h | 3 +-- .../application/src/rfid/reader/hf/mf1_toolbox.c | 14 -------------- .../application/src/rfid/reader/hf/mf1_toolbox.h | 1 - software/script/chameleon_cmd.py | 15 +-------------- 6 files changed, 7 insertions(+), 47 deletions(-) diff --git a/docs/protocol.md b/docs/protocol.md index ef476e58..d3199398 100644 --- a/docs/protocol.md +++ b/docs/protocol.md @@ -211,10 +211,10 @@ Notes: * Command: no data * Response: 1 byte, according to `mf1_nested_type_t` enum * CLI: cf `hf 14a info` -### 2003: MF1_DETECT_DARKSIDE -* Command: no data -* Response: 1 byte, according to `mf1_darkside_status_t` enum -* CLI: unused +### 2003: MF1_STATIC_NESTED_ACQUIRE +* Command: 10 bytes: `type_known|block_known|key_known[6]|type_target|block_target`. Key as 6 bytes. +* Response: 4+N*8 bytes: `uid[4]` followed by N tuples of `nt[4]|nt_enc[4]`. All values as U32. +* CLI: cf `hf mf nested` on static nonce tag ### 2004: MF1_DARKSIDE_ACQUIRE * Command: 4 bytes: `type_target|block_target|first_recover|sync_max` * Response: 1 byte if Darkside failed, according to `mf1_darkside_status_t` enum, diff --git a/firmware/application/src/app_cmd.c b/firmware/application/src/app_cmd.c index 1fbd36f8..93518efc 100644 --- a/firmware/application/src/app_cmd.c +++ b/firmware/application/src/app_cmd.c @@ -256,15 +256,6 @@ static data_frame_tx_t *cmd_processor_mf1_detect_prng(uint16_t cmd, uint16_t sta return data_frame_make(cmd, HF_TAG_OK, sizeof(type), &type); } -static data_frame_tx_t *cmd_processor_mf1_detect_darkside(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - uint8_t darkside_status; - status = check_darkside_support((mf1_darkside_status_t *)&darkside_status); - if (status != HF_TAG_OK) { - return data_frame_make(cmd, status, 0, NULL); - } - return data_frame_make(cmd, HF_TAG_OK, sizeof(darkside_status), &darkside_status); -} - static data_frame_tx_t *cmd_processor_mf1_darkside_acquire(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { if (length != 4) { return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); @@ -938,8 +929,7 @@ static cmd_data_map_t m_data_cmd_map[] = { { DATA_CMD_HF14A_SCAN, before_hf_reader_run, cmd_processor_hf14a_scan, after_hf_reader_run }, { DATA_CMD_MF1_DETECT_SUPPORT, before_hf_reader_run, cmd_processor_mf1_detect_support, after_hf_reader_run }, { DATA_CMD_MF1_DETECT_PRNG, before_hf_reader_run, cmd_processor_mf1_detect_prng, after_hf_reader_run }, - { DATA_CMD_MF1_DETECT_DARKSIDE, before_hf_reader_run, cmd_processor_mf1_detect_darkside, after_hf_reader_run }, - + { DATA_CMD_MF1_STATIC_NESTED_ACQUIRE, before_hf_reader_run, cmd_processor_mf1_static_nested_acquire, after_hf_reader_run }, { DATA_CMD_MF1_DARKSIDE_ACQUIRE, before_hf_reader_run, cmd_processor_mf1_darkside_acquire, after_hf_reader_run }, { DATA_CMD_MF1_DETECT_NT_DIST, before_hf_reader_run, cmd_processor_mf1_detect_nt_dist, after_hf_reader_run }, { DATA_CMD_MF1_NESTED_ACQUIRE, before_hf_reader_run, cmd_processor_mf1_nested_acquire, after_hf_reader_run }, @@ -947,7 +937,6 @@ static cmd_data_map_t m_data_cmd_map[] = { { DATA_CMD_MF1_AUTH_ONE_KEY_BLOCK, before_hf_reader_run, cmd_processor_mf1_auth_one_key_block, after_hf_reader_run }, { DATA_CMD_MF1_READ_ONE_BLOCK, before_hf_reader_run, cmd_processor_mf1_read_one_block, after_hf_reader_run }, { DATA_CMD_MF1_WRITE_ONE_BLOCK, before_hf_reader_run, cmd_processor_mf1_write_one_block, after_hf_reader_run }, - { DATA_CMD_MF1_STATIC_NESTED_ACQUIRE, before_hf_reader_run, cmd_processor_mf1_static_nested_acquire, after_hf_reader_run }, { DATA_CMD_EM410X_SCAN, before_reader_run, cmd_processor_em410x_scan, NULL }, { DATA_CMD_EM410X_WRITE_TO_T55XX, before_reader_run, cmd_processor_em410x_write_to_t55XX, NULL }, diff --git a/firmware/application/src/data_cmd.h b/firmware/application/src/data_cmd.h index e04e93c7..ce8efc8a 100644 --- a/firmware/application/src/data_cmd.h +++ b/firmware/application/src/data_cmd.h @@ -57,14 +57,13 @@ #define DATA_CMD_HF14A_SCAN (2000) #define DATA_CMD_MF1_DETECT_SUPPORT (2001) #define DATA_CMD_MF1_DETECT_PRNG (2002) -#define DATA_CMD_MF1_DETECT_DARKSIDE (2003) +#define DATA_CMD_MF1_STATIC_NESTED_ACQUIRE (2003) #define DATA_CMD_MF1_DARKSIDE_ACQUIRE (2004) #define DATA_CMD_MF1_DETECT_NT_DIST (2005) #define DATA_CMD_MF1_NESTED_ACQUIRE (2006) #define DATA_CMD_MF1_AUTH_ONE_KEY_BLOCK (2007) #define DATA_CMD_MF1_READ_ONE_BLOCK (2008) #define DATA_CMD_MF1_WRITE_ONE_BLOCK (2009) -#define DATA_CMD_MF1_STATIC_NESTED_ACQUIRE (2010) // // ****************************************************************** diff --git a/firmware/application/src/rfid/reader/hf/mf1_toolbox.c b/firmware/application/src/rfid/reader/hf/mf1_toolbox.c index 61f1a61a..358d1b55 100644 --- a/firmware/application/src/rfid/reader/hf/mf1_toolbox.c +++ b/firmware/application/src/rfid/reader/hf/mf1_toolbox.c @@ -569,20 +569,6 @@ void antenna_switch_delay(uint32_t delay_ms) { g_ant_reset_delay = delay_ms; } -/** -* @brief :Determine whether this card supports DARKSIDE attack -* @retval : If support, return hf_tag_ok and darkside_status = OK. If it is not supported, -* Return to the results of abnormal results during the detection process in darkside_status -* Or other card -related communication errors, the most common is loss card HF_TAG_NO -* -*/ -uint8_t check_darkside_support(mf1_darkside_status_t *darkside_status) { - // Instantiated parameter - DarksideCore_t dc; - //Determine and return the result directly - return darkside_recover_key(0x03, PICC_AUTHENT1A, true, 0x15, &dc, darkside_status); -} - /** * @brief : Determine whether this card supports M1 verification steps * @retval : If support, it will return hf_tag_ok, diff --git a/firmware/application/src/rfid/reader/hf/mf1_toolbox.h b/firmware/application/src/rfid/reader/hf/mf1_toolbox.h index 40b35ac9..5f336ae3 100644 --- a/firmware/application/src/rfid/reader/hf/mf1_toolbox.h +++ b/firmware/application/src/rfid/reader/hf/mf1_toolbox.h @@ -90,7 +90,6 @@ uint8_t nested_distance_detect( uint8_t nested_recover_key(NESTED_CORE_PARAM_DEF, mf1_nested_core_t ncs[SETS_NR]); uint8_t static_nested_recover_key(NESTED_CORE_PARAM_DEF, mf1_static_nested_core_t* sncs); -uint8_t check_darkside_support(mf1_darkside_status_t *darkside_status); uint8_t check_prng_type(mf1_prng_type_t *type); uint8_t check_std_mifare_nt_support(bool *support); void antenna_switch_delay(uint32_t delay_ms); diff --git a/software/script/chameleon_cmd.py b/software/script/chameleon_cmd.py index 2f8ef128..a4dcd2ce 100644 --- a/software/script/chameleon_cmd.py +++ b/software/script/chameleon_cmd.py @@ -61,15 +61,13 @@ DATA_CMD_HF14A_SCAN = 2000 DATA_CMD_MF1_DETECT_SUPPORT = 2001 DATA_CMD_MF1_DETECT_PRNG = 2002 -# FIXME: implemented but unused in CLI commands -DATA_CMD_MF1_DETECT_DARKSIDE = 2003 +DATA_CMD_MF1_STATIC_NESTED_ACQUIRE = 2003 DATA_CMD_MF1_DARKSIDE_ACQUIRE = 2004 DATA_CMD_MF1_DETECT_NT_DIST = 2005 DATA_CMD_MF1_NESTED_ACQUIRE = 2006 DATA_CMD_MF1_AUTH_ONE_KEY_BLOCK = 2007 DATA_CMD_MF1_READ_ONE_BLOCK = 2008 DATA_CMD_MF1_WRITE_ONE_BLOCK = 2009 -DATA_CMD_MF1_STATIC_NESTED_ACQUIRE = 2010 DATA_CMD_EM410X_SCAN = 3000 DATA_CMD_EM410X_WRITE_TO_T55XX = 3001 @@ -465,17 +463,6 @@ def mf1_detect_prng(self): resp.data = resp.data[0] return resp - @expect_response(chameleon_status.Device.HF_TAG_OK) - def mf1_detect_darkside_support(self): - """ - Check if the card is vulnerable to mifare classic darkside attack - :return: - """ - resp = self.device.send_cmd_sync(DATA_CMD_MF1_DETECT_DARKSIDE, timeout=20) - if resp.status == chameleon_status.Device.HF_TAG_OK: - resp.data = resp.data[0] - return resp - @expect_response(chameleon_status.Device.HF_TAG_OK) def mf1_detect_nt_dist(self, block_known, type_known, key_known): """ From 52953de9274c12a78e3ec5131007ede55bffc78b Mon Sep 17 00:00:00 2001 From: Philippe Teuwen Date: Tue, 19 Sep 2023 21:37:56 +0200 Subject: [PATCH 13/38] changelog & typos in protocol --- CHANGELOG.md | 2 ++ docs/protocol.md | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1025dd98..422c2096 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ All notable changes to this project will be documented in this file. This project uses the changelog in accordance with [keepchangelog](http://keepachangelog.com/). Please use this to write notable changes, which is not the same as git commit log... ## [unreleased][unreleased] + - Removed MF1_DETECT_DARKSIDE (@doegox) + - Added MF1_STATIC_NESTED_ACQUIRE and its support in `hf mf nested` (@xianglin1998) - Changed `hf 14a scan`: Automatically send RATS to 14443-4a tags (@augustozanellato) - Changed Darkside: use LEDs for visual feedback of attack progression (@doegox) - Changed Darkside: longer RF field off for reset and longer CLI timeout (@doegox) diff --git a/docs/protocol.md b/docs/protocol.md index d3199398..9b57a341 100644 --- a/docs/protocol.md +++ b/docs/protocol.md @@ -218,12 +218,12 @@ Notes: ### 2004: MF1_DARKSIDE_ACQUIRE * Command: 4 bytes: `type_target|block_target|first_recover|sync_max` * Response: 1 byte if Darkside failed, according to `mf1_darkside_status_t` enum, - else 25 bytes `darkside_status|uid[4]|nt1[4]|par[4]|ks1[4]|nr[4]|ar[4]` + else 33 bytes `darkside_status|uid[4]|nt1[4]|par[8]|ks1[8]|nr[4]|ar[4]` * `darkside_status` * `uid[4]` U32 (format expected by `darkside` tool) * `nt1[4]` U32 - * `par[4]` U32 - * `ks1[4]` U32 + * `par[4]` U64 + * `ks1[4]` U64 * `nr[4]` U32 * `ar[4]` U32 * CLI: cf `hf mf darkside` From 3889033815668186876123e7721bc3112a89a93d Mon Sep 17 00:00:00 2001 From: Philippe Teuwen Date: Tue, 19 Sep 2023 21:47:37 +0200 Subject: [PATCH 14/38] sigh --- docs/protocol.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/protocol.md b/docs/protocol.md index 9b57a341..d508215a 100644 --- a/docs/protocol.md +++ b/docs/protocol.md @@ -222,8 +222,8 @@ Notes: * `darkside_status` * `uid[4]` U32 (format expected by `darkside` tool) * `nt1[4]` U32 - * `par[4]` U64 - * `ks1[4]` U64 + * `par[8]` U64 + * `ks1[8]` U64 * `nr[4]` U32 * `ar[4]` U32 * CLI: cf `hf mf darkside` From ebb77dc93878ddc26bc3177d8abf0d302f74269a Mon Sep 17 00:00:00 2001 From: Philippe Teuwen Date: Tue, 19 Sep 2023 22:02:49 +0200 Subject: [PATCH 15/38] recode GB18030..UTF-8 staticnested.c --- software/src/staticnested.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/software/src/staticnested.c b/software/src/staticnested.c index d1ba0bda..5fffc254 100644 --- a/software/src/staticnested.c +++ b/software/src/staticnested.c @@ -23,11 +23,11 @@ int main(int argc, char *const argv[]) { // Which generation of ST tags is detected. if (!check_st_level_at_sirst_run) { if (nt1 == 0x01200145) { - // ·¢ÏÖÒ»´úÎÞ©¶´£¬´Ë±êÇ©È«¿¨¿ÉÓÃĬÈÏ160µÄ²ÎÊý½øÐнâÃÜ£¡ + // å‘现一代无æ¼æ´žï¼Œæ­¤æ ‡ç­¾å…¨å¡å¯ç”¨é»˜è®¤160çš„å‚æ•°è¿›è¡Œè§£å¯†ï¼ dist = 160; // st gen1 } else if (nt1 == 0x009080A2) { // st gen2 - // ·¢ÏÖÎÞ©¶´¶þ´ú£¬ÎÒÃÇÈç¹ûÈ·ÈÏÄ¿Ç°ÐèÒª¹¥»÷µÄʱBÃÜÔ¿£¬ÄÇô¾ÍÐèÒª¸ü»»¹¥»÷²ÎÊý + // å‘现无æ¼æ´žäºŒä»£ï¼Œæˆ‘们如果确认目å‰éœ€è¦æ”»å‡»çš„æ—¶B密钥,那么就需è¦æ›´æ¢æ”»å‡»å‚æ•° if (type == 0x61) { dist = 161; } From cd73ac89eec06e202d26cf780e819d8bf2c43888 Mon Sep 17 00:00:00 2001 From: Philippe Teuwen Date: Tue, 19 Sep 2023 22:09:15 +0200 Subject: [PATCH 16/38] staticnested.c: translate comments, fix typo --- software/src/staticnested.c | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/software/src/staticnested.c b/software/src/staticnested.c index 5fffc254..094249c6 100644 --- a/software/src/staticnested.c +++ b/software/src/staticnested.c @@ -14,20 +14,20 @@ int main(int argc, char *const argv[]) { uint8_t type = (uint8_t)atoui(argv[2]); // target key type // process all args. - bool check_st_level_at_sirst_run = false; + bool check_st_level_at_first_run = false; for (i = 3, j = 0; i < argc; i += 2) { // nt + par nt1 = atoui(argv[i]); nt2 = atoui(argv[i + 1]); - // Which generation of ST tags is detected. - if (!check_st_level_at_sirst_run) { + // Which generation of static tag is detected. + if (!check_st_level_at_first_run) { if (nt1 == 0x01200145) { - // å‘现一代无æ¼æ´žï¼Œæ­¤æ ‡ç­¾å…¨å¡å¯ç”¨é»˜è®¤160çš„å‚æ•°è¿›è¡Œè§£å¯†ï¼ + // There is no loophole in this generation. + // This tag can be decrypted with the default parameter value 160! dist = 160; // st gen1 - } - else if (nt1 == 0x009080A2) { // st gen2 - // å‘现无æ¼æ´žäºŒä»£ï¼Œæˆ‘们如果确认目å‰éœ€è¦æ”»å‡»çš„æ—¶B密钥,那么就需è¦æ›´æ¢æ”»å‡»å‚æ•° + } else if (nt1 == 0x009080A2) { // st gen2 + // We found that the gen2 tag is vulnerable too but parameter must be adapted depending on the attacked key if (type == 0x61) { dist = 161; } @@ -35,15 +35,14 @@ int main(int argc, char *const argv[]) { dist = 160; } else { - // can't to here!!! + // can't be here!!! goto error; } - } - else { - // can't to here!!! + } else { + // can't be here!!! goto error; } - check_st_level_at_sirst_run = true; + check_st_level_at_first_run = true; } nttest = prng_successor(nt1, dist); From 14bffafb2a07b188addc6cfd9cb391bca183b3a0 Mon Sep 17 00:00:00 2001 From: Philippe Teuwen Date: Wed, 20 Sep 2023 01:26:47 +0200 Subject: [PATCH 17/38] style --- .../src/rfid/reader/hf/mf1_toolbox.c | 132 +++++++++--------- .../src/rfid/reader/hf/mf1_toolbox.h | 4 +- .../application/src/rfid/reader/hf/rc522.c | 4 +- software/script/chameleon_cli_unit.py | 52 ++++--- software/script/chameleon_cmd.py | 23 ++- software/script/chameleon_utils.py | 2 +- software/src/common.c | 4 +- software/src/common.h | 4 +- software/src/nested_util.c | 57 ++++---- software/src/nested_util.h | 6 +- software/src/staticnested.c | 8 +- 11 files changed, 148 insertions(+), 148 deletions(-) diff --git a/firmware/application/src/rfid/reader/hf/mf1_toolbox.c b/firmware/application/src/rfid/reader/hf/mf1_toolbox.c index 358d1b55..473262fc 100644 --- a/firmware/application/src/rfid/reader/hf/mf1_toolbox.c +++ b/firmware/application/src/rfid/reader/hf/mf1_toolbox.c @@ -273,7 +273,7 @@ static uint8_t darkside_select_nonces(picc_14a_tag_t *tag, uint8_t block, uint8_ //Random number collection for (i = 0; i < NT_COUNT; i++) { bsp_wdt_feed(); - while(NRF_LOG_PROCESS()); + while (NRF_LOG_PROCESS()); //When the antenna is reset, we must make sure // 1. The antenna is powered off for a long time to ensure that the card is completely powered off, otherwise the pseudo -random number generator of the card cannot be reset // 2. Moderate power -off time, don't be too long, it will affect efficiency, and don't be too short. @@ -419,7 +419,7 @@ uint8_t darkside_recover_key(uint8_t targetBlk, uint8_t targetTyp, // Always collect different NACK under a large cycle do { bsp_wdt_feed(); - while(NRF_LOG_PROCESS()); + while (NRF_LOG_PROCESS()); // update LEDs led_toggle ^= 1; if (led_toggle) { @@ -492,7 +492,7 @@ uint8_t darkside_recover_key(uint8_t targetBlk, uint8_t targetTyp, resync_count = 0; if (len == 4) { - NRF_LOG_INFO("NACK acquired (%i/8)", nt_diff+1); + NRF_LOG_INFO("NACK acquired (%i/8)", nt_diff + 1); received_nack = 1; } else if (len == 32) { // did we get lucky and got our dummy key to be valid? @@ -928,80 +928,80 @@ uint8_t nested_distance_detect(uint8_t block, uint8_t type, uint8_t *key, uint8_ } /** -* @brief : StaticNested core, used to collect NT. +* @brief : StaticNested core, used to collect NT. * This function is only responsible for collection and is not responsible for converting and parsing to KS. -* @param :p_nt1 : NT1, non encrypted. -* @param :p_nt2 : NT2, encrypted. -* @param :keyKnown : U64 value of the known key of the card -* @param :blkKnown : The sector to which the card's known secret key belongs -* @param :typKnown : The known key type of the card, 0x60 (A key) or 0x61 (B key) -* @param :targetBlock : Target sectors that require nested attacks -* @param :targetType : Target key types that require nested attacks -* @param :nestedAgain : StaticNested enhanced vulnerability, which can obtain two sets of encrypted random numbers based on nested verification of known keys -* @retval : Successfully collected and returned to HF_TAG_OK, otherwise an error code will be returned. +* @param :p_nt1 : NT1, non encrypted. +* @param :p_nt2 : NT2, encrypted. +* @param :keyKnown : U64 value of the known key of the card +* @param :blkKnown : The sector to which the card's known secret key belongs +* @param :typKnown : The known key type of the card, 0x60 (A key) or 0x61 (B key) +* @param :targetBlock : Target sectors that require nested attacks +* @param :targetType : Target key types that require nested attacks +* @param :nestedAgain : StaticNested enhanced vulnerability, which can obtain two sets of encrypted random numbers based on nested verification of known keys +* @retval : Successfully collected and returned to HF_TAG_OK, otherwise an error code will be returned. * */ uint8_t static_nested_recover_core(uint8_t *p_nt1, uint8_t *p_nt2, uint64_t keyKnown, uint8_t blkKnown, uint8_t typKnown, uint8_t targetBlock, uint8_t targetType, uint8_t nestedAgain) { struct Crypto1State mpcs = {0, 0}; - struct Crypto1State *pcs = &mpcs; - uint8_t status, len; - uint8_t parity[4] = {0x00}; - uint8_t answer[4] = {0x00}; - uint32_t uid, nt1, nt2; - uid = get_u32_tag_uid(p_tag_info); - pcd_14a_reader_halt_tag(); - if (pcd_14a_reader_scan_auto(p_tag_info) != HF_TAG_OK) { - return HF_TAG_NO; - } - status = authex(pcs, uid, blkKnown, typKnown, keyKnown, AUTH_FIRST, &nt1); - if (status != HF_TAG_OK) { - return MF_ERR_AUTH; - } - if (nestedAgain) { - status = authex(pcs, uid, blkKnown, typKnown, keyKnown, AUTH_NESTED, NULL); - if (status != HF_TAG_OK) { - return MF_ERR_AUTH; - } - } - len = send_cmd(pcs, AUTH_NESTED, targetType, targetBlock, &status, answer, parity, U8ARR_BIT_LEN(answer)); - if (len != 32) { - NRF_LOG_INFO("No 32 data recv on sendcmd: %d\r\n", len); - return HF_ERR_STAT; - } - nt2 = bytes_to_num(answer, 4); - num_to_bytes(nt1, 4, p_nt1); - num_to_bytes(nt2, 4, p_nt2); - return HF_TAG_OK; + struct Crypto1State *pcs = &mpcs; + uint8_t status, len; + uint8_t parity[4] = {0x00}; + uint8_t answer[4] = {0x00}; + uint32_t uid, nt1, nt2; + uid = get_u32_tag_uid(p_tag_info); + pcd_14a_reader_halt_tag(); + if (pcd_14a_reader_scan_auto(p_tag_info) != HF_TAG_OK) { + return HF_TAG_NO; + } + status = authex(pcs, uid, blkKnown, typKnown, keyKnown, AUTH_FIRST, &nt1); + if (status != HF_TAG_OK) { + return MF_ERR_AUTH; + } + if (nestedAgain) { + status = authex(pcs, uid, blkKnown, typKnown, keyKnown, AUTH_NESTED, NULL); + if (status != HF_TAG_OK) { + return MF_ERR_AUTH; + } + } + len = send_cmd(pcs, AUTH_NESTED, targetType, targetBlock, &status, answer, parity, U8ARR_BIT_LEN(answer)); + if (len != 32) { + NRF_LOG_INFO("No 32 data recv on sendcmd: %d\r\n", len); + return HF_ERR_STAT; + } + nt2 = bytes_to_num(answer, 4); + num_to_bytes(nt1, 4, p_nt1); + num_to_bytes(nt2, 4, p_nt2); + return HF_TAG_OK; } /** -* @brief : StaticNested encapsulates and calls the functions implemented by the core to collect 2 sets of random numbers. +* @brief : StaticNested encapsulates and calls the functions implemented by the core to collect 2 sets of random numbers. * This function is only responsible for collection and is not responsible for converting and parsing to KS. -* @param :keyKnown : U64 value of the known key of the card -* @param :blkKnown : The sector to which the card's known secret key belongs -* @param :typKnown : The known key type of the card, 0x60 (A key) or 0x61 (B key) -* @param :targetBlock : Target sectors that require nested attacks -* @param :targetType : Target key type that require nested attacks -* @param :sncs : StaticNested Decrypting Core Structure Array -* @retval : Successfully collected and returned to HF_TAG_OK, otherwise an error code will be returned. +* @param :keyKnown : U64 value of the known key of the card +* @param :blkKnown : The sector to which the card's known secret key belongs +* @param :typKnown : The known key type of the card, 0x60 (A key) or 0x61 (B key) +* @param :targetBlock : Target sectors that require nested attacks +* @param :targetType : Target key type that require nested attacks +* @param :sncs : StaticNested Decrypting Core Structure Array +* @retval : Successfully collected and returned to HF_TAG_OK, otherwise an error code will be returned. * */ -uint8_t static_nested_recover_key(uint64_t keyKnown, uint8_t blkKnown, uint8_t typKnown, uint8_t targetBlock, uint8_t targetType, mf1_static_nested_core_t* sncs) { - uint8_t res; - res = pcd_14a_reader_scan_auto(p_tag_info); - if (res!= HF_TAG_OK) { - return res; - } - get_4byte_tag_uid(p_tag_info, sncs->uid); - res = static_nested_recover_core(sncs->core[0].nt1, sncs->core[0].nt2, keyKnown, blkKnown, typKnown, targetBlock, targetType, false); - if (res != HF_TAG_OK) { - return res; - } - res = static_nested_recover_core(sncs->core[1].nt1, sncs->core[1].nt2, keyKnown, blkKnown, typKnown, targetBlock, targetType, true); - if (res != HF_TAG_OK) { - return res; - } - return HF_TAG_OK; +uint8_t static_nested_recover_key(uint64_t keyKnown, uint8_t blkKnown, uint8_t typKnown, uint8_t targetBlock, uint8_t targetType, mf1_static_nested_core_t *sncs) { + uint8_t res; + res = pcd_14a_reader_scan_auto(p_tag_info); + if (res != HF_TAG_OK) { + return res; + } + get_4byte_tag_uid(p_tag_info, sncs->uid); + res = static_nested_recover_core(sncs->core[0].nt1, sncs->core[0].nt2, keyKnown, blkKnown, typKnown, targetBlock, targetType, false); + if (res != HF_TAG_OK) { + return res; + } + res = static_nested_recover_core(sncs->core[1].nt1, sncs->core[1].nt2, keyKnown, blkKnown, typKnown, targetBlock, targetType, true); + if (res != HF_TAG_OK) { + return res; + } + return HF_TAG_OK; } /** diff --git a/firmware/application/src/rfid/reader/hf/mf1_toolbox.h b/firmware/application/src/rfid/reader/hf/mf1_toolbox.h index 5f336ae3..0f28a26f 100644 --- a/firmware/application/src/rfid/reader/hf/mf1_toolbox.h +++ b/firmware/application/src/rfid/reader/hf/mf1_toolbox.h @@ -32,7 +32,7 @@ typedef struct { //Answer the random number parameters required for N } mf1_nested_core_t; typedef struct { - uint8_t uid[4]; + uint8_t uid[4]; struct { uint8_t nt1[4]; uint8_t nt2[4]; @@ -88,7 +88,7 @@ uint8_t nested_distance_detect( uint8_t targetType \ uint8_t nested_recover_key(NESTED_CORE_PARAM_DEF, mf1_nested_core_t ncs[SETS_NR]); -uint8_t static_nested_recover_key(NESTED_CORE_PARAM_DEF, mf1_static_nested_core_t* sncs); +uint8_t static_nested_recover_key(NESTED_CORE_PARAM_DEF, mf1_static_nested_core_t *sncs); uint8_t check_prng_type(mf1_prng_type_t *type); uint8_t check_std_mifare_nt_support(bool *support); diff --git a/firmware/application/src/rfid/reader/hf/rc522.c b/firmware/application/src/rfid/reader/hf/rc522.c index 0aec993d..be7d2375 100644 --- a/firmware/application/src/rfid/reader/hf/rc522.c +++ b/firmware/application/src/rfid/reader/hf/rc522.c @@ -583,7 +583,7 @@ uint8_t pcd_14a_reader_scan_once(picc_14a_tag_t *tag) { if (tag->sak & 0x20) { // Tag supports 14443-4, sending RATS uint16_t ats_size; - status = pcd_14a_reader_ats_request(tag->ats, &ats_size, 0xFF*8); + status = pcd_14a_reader_ats_request(tag->ats, &ats_size, 0xFF * 8); ats_size -= 2; // size returned by pcd_14a_reader_ats_request includes CRC if (ats_size > 254) { NRF_LOG_INFO("Invalid ATS > 254!"); @@ -600,7 +600,7 @@ uint8_t pcd_14a_reader_scan_once(picc_14a_tag_t *tag) { // return HF_ERR_ATS; } /* - * FIXME: If there is an issue here, it will cause the label to lose its selected state. + * FIXME: If there is an issue here, it will cause the label to lose its selected state. * It is necessary to reselect the card after the issue occurs here. */ } diff --git a/software/script/chameleon_cli_unit.py b/software/script/chameleon_cli_unit.py index ea592e72..9d08c39e 100644 --- a/software/script/chameleon_cli_unit.py +++ b/software/script/chameleon_cli_unit.py @@ -382,7 +382,7 @@ def args_parser(self) -> ArgumentParserNoExit or None: help="The type of the target block to recover") # hf mf nested -o --block-known 0 --type-known A --key FFFFFFFFFFFF --block-target 4 --type-target A return parser - + def from_nt_level_code_to_str(self, nt_level): if nt_level == 0: return 'StaticNested' @@ -409,9 +409,10 @@ def recover_a_key(self, block_known, type_known, key_known, block_target, type_t return None # acquire - if nt_level == 0: # It's a staticnested tag? - nt_uid_obj = self.cmd.mf1_static_nested_acquire(block_known, type_known, key_known, block_target, type_target) - cmd_param = f"{nt_uid_obj['uid']} {str(type_target)}" + if nt_level == 0: # It's a staticnested tag? + nt_uid_obj = self.cmd.mf1_static_nested_acquire( + block_known, type_known, key_known, block_target, type_target) + cmd_param = f"{nt_uid_obj['uid']} {str(type_target)}" for nt_item in nt_uid_obj['nts']: cmd_param += f" {nt_item['nt']} {nt_item['nt_enc']}" decryptor_name = "staticnested" @@ -419,11 +420,11 @@ def recover_a_key(self, block_known, type_known, key_known, block_target, type_t dist_obj = self.cmd.mf1_detect_nt_dist(block_known, type_known, key_known) nt_obj = self.cmd.mf1_nested_acquire(block_known, type_known, key_known, block_target, type_target) # create cmd - cmd_param = f"{dist_obj['uid']} {dist_obj['dist']}" + cmd_param = f"{dist_obj['uid']} {dist_obj['dist']}" for nt_item in nt_obj: cmd_param += f" {nt_item['nt']} {nt_item['nt_enc']} {nt_item['par']}" decryptor_name = "nested" - + # Cross-platform compatibility if sys.platform == "win32": cmd_recover = f"{decryptor_name}.exe {cmd_param}" @@ -517,7 +518,7 @@ def recover_key(self, block_target, type_target): print(f"Darkside error: {chameleon_cmd.MifareClassicDarksideStatus(darkside_resp[0])}") break darkside_obj = darkside_resp[1] - + if darkside_obj['par'] != 0: # NXP tag workaround. self.darkside_list.clear() @@ -1054,12 +1055,12 @@ def args_parser(self) -> ArgumentParserNoExit or None: def get_slot_name(self, slot, sense): try: name = self.cmd.get_slot_tag_nick(slot, sense).decode(encoding="utf8") - return len(name),len(CC+C0),f'{CC}{name}{C0}' + return len(name), len(CC+C0), f'{CC}{name}{C0}' except UnexpectedResponseError: - return 0,0,'' + return 0, 0, '' except UnicodeDecodeError: name = "UTF8 Err" - return len(name),len(CR+C0),f'{CR}{name}{C0}' + return len(name), len(CR+C0), f'{CR}{name}{C0}' # hw slot list def on_exec(self, args: argparse.Namespace): @@ -1086,20 +1087,24 @@ def on_exec(self, args: argparse.Namespace): f'{(slotnames[fwslot][0][2] if args.extend else ""):{maxnamelength+slotnames[fwslot][0][1]+1 if args.extend else maxnamelength+1}}' f'{f"{CY if enabled[fwslot] else C0}{hf_tag_type}{C0}" if hf_tag_type != chameleon_cmd.TagSpecificType.TAG_TYPE_UNKNOWN else "undef"}') if args.extend == 1 and \ - enabled[fwslot] and \ - slot == selected and \ - hf_tag_type in [ - chameleon_cmd.TagSpecificType.TAG_TYPE_MIFARE_Mini, - chameleon_cmd.TagSpecificType.TAG_TYPE_MIFARE_1024, - chameleon_cmd.TagSpecificType.TAG_TYPE_MIFARE_2048, - chameleon_cmd.TagSpecificType.TAG_TYPE_MIFARE_4096, - ]: + enabled[fwslot] and \ + slot == selected and \ + hf_tag_type in [ + chameleon_cmd.TagSpecificType.TAG_TYPE_MIFARE_Mini, + chameleon_cmd.TagSpecificType.TAG_TYPE_MIFARE_1024, + chameleon_cmd.TagSpecificType.TAG_TYPE_MIFARE_2048, + chameleon_cmd.TagSpecificType.TAG_TYPE_MIFARE_4096, + ]: config = self.cmd.mf1_get_emulator_config() print(' - Mifare Classic emulator settings:') - print(f' {"Detection (mfkey32) mode:":40}{f"{CG}enabled{C0}" if config["detection"] else f"{CR}disabled{C0}"}') - print(f' {"Gen1A magic mode:":40}{f"{CG}enabled{C0}" if config["gen1a_mode"] else f"{CR}disabled{C0}"}') - print(f' {"Gen2 magic mode:":40}{f"{CG}enabled{C0}" if config["gen2_mode"] else f"{CR}disabled{C0}"}') - print(f' {"Use anti-collision data from block 0:":40}{f"{CG}enabled{C0}" if config["block_anti_coll_mode"] else f"{CR}disabled{C0}"}') + print( + f' {"Detection (mfkey32) mode:":40}{f"{CG}enabled{C0}" if config["detection"] else f"{CR}disabled{C0}"}') + print( + f' {"Gen1A magic mode:":40}{f"{CG}enabled{C0}" if config["gen1a_mode"] else f"{CR}disabled{C0}"}') + print( + f' {"Gen2 magic mode:":40}{f"{CG}enabled{C0}" if config["gen2_mode"] else f"{CR}disabled{C0}"}') + print( + f' {"Use anti-collision data from block 0:":40}{f"{CG}enabled{C0}" if config["block_anti_coll_mode"] else f"{CR}disabled{C0}"}') print(f' {"Write mode:":40}{CY}{chameleon_cmd.MifareClassicWriteMode(config["write_mode"])}{C0}') print(f' LF: ' f'{(slotnames[fwslot][1][2] if args.extend else ""):{maxnamelength+slotnames[fwslot][1][1]+1 if args.extend else maxnamelength+1}}' @@ -1555,7 +1560,8 @@ def args_parser(self) -> ArgumentParserNoExit or None: return parser def on_exec(self, args: argparse.Namespace): - response = self.cmd.device.send_cmd_sync(args.command, data=bytes.fromhex(args.data), status=0x0, timeout=args.timeout) + response = self.cmd.device.send_cmd_sync( + args.command, data=bytes.fromhex(args.data), status=0x0, timeout=args.timeout) print(" - Received:") print(f" Command: {response.cmd}") status_string = f" Status: {response.status:#02x}" diff --git a/software/script/chameleon_cmd.py b/software/script/chameleon_cmd.py index a4dcd2ce..f12695c0 100644 --- a/software/script/chameleon_cmd.py +++ b/software/script/chameleon_cmd.py @@ -489,7 +489,6 @@ def mf1_nested_acquire(self, block_known, type_known, key_known, block_target, t for nt, nt_enc, par in struct.iter_unpack('!IIB', resp.data)] return resp - @expect_response(chameleon_status.Device.HF_TAG_OK) def mf1_darkside_acquire(self, block_target, type_target, first_recover: int or bool, sync_max): """ @@ -550,7 +549,7 @@ def mf1_write_one_block(self, block, type_value, key, block_data): resp = self.device.send_cmd_sync(DATA_CMD_MF1_WRITE_ONE_BLOCK, data) resp.data = resp.status == chameleon_status.Device.HF_TAG_OK return resp - + @expect_response(chameleon_status.Device.HF_TAG_OK) def mf1_static_nested_acquire(self, block_known, type_known, key_known, block_target, type_target): """ @@ -564,7 +563,7 @@ def mf1_static_nested_acquire(self, block_known, type_known, key_known, block_ta 'uid': struct.unpack('!I', resp.data[0:4])[0], 'nts': [ { - 'nt': nt, + 'nt': nt, 'nt_enc': nt_enc } for nt, nt_enc in struct.iter_unpack('!II', resp.data[4:]) ] @@ -1047,20 +1046,20 @@ def get_device_settings(self): if resp.status == chameleon_status.Device.STATUS_DEVICE_SUCCESS: if resp.data[0] > CURRENT_VERSION_SETTINGS: raise ValueError("Settings version in app older than Chameleon. " - "Please upgrade client") + "Please upgrade client") if resp.data[0] < CURRENT_VERSION_SETTINGS: raise ValueError("Settings version in app newer than Chameleon. " - "Please upgrade Chameleon firmware") + "Please upgrade Chameleon firmware") settings_version, animation_mode, btn_press_A, btn_press_B, btn_long_press_A, btn_long_press_B, ble_pairing_enable, ble_pairing_key = struct.unpack( '!BBBBBBB6s', resp.data) resp.data = {'settings_version': settings_version, - 'animation_mode': animation_mode, - 'btn_press_A': btn_press_A, - 'btn_press_B': btn_press_B, - 'btn_long_press_A': btn_long_press_A, - 'btn_long_press_B': btn_long_press_B, - 'ble_pairing_enable': ble_pairing_enable, - 'ble_pairing_key': ble_pairing_key} + 'animation_mode': animation_mode, + 'btn_press_A': btn_press_A, + 'btn_press_B': btn_press_B, + 'btn_long_press_A': btn_long_press_A, + 'btn_long_press_B': btn_long_press_B, + 'ble_pairing_enable': ble_pairing_enable, + 'ble_pairing_key': ble_pairing_key} return resp @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) diff --git a/software/script/chameleon_utils.py b/software/script/chameleon_utils.py index bca09b40..089992fe 100644 --- a/software/script/chameleon_utils.py +++ b/software/script/chameleon_utils.py @@ -61,7 +61,7 @@ def error_throwing_func(*args, **kwargs): else: raise UnexpectedResponseError( f"Unexpected response and unknown status {ret.status}") - + return ret.data return error_throwing_func diff --git a/software/src/common.c b/software/src/common.c index e405d1ca..0b9cc88d 100644 --- a/software/src/common.c +++ b/software/src/common.c @@ -1,7 +1,7 @@ #include -uint64_t atoui(const char* str) { +uint64_t atoui(const char *str) { uint64_t result = 0; for (int i = 0; str[i] != '\0'; ++i) { @@ -12,7 +12,7 @@ uint64_t atoui(const char* str) { return result; } -void num_to_bytes(uint64_t n, uint32_t len, uint8_t* dest) { +void num_to_bytes(uint64_t n, uint32_t len, uint8_t *dest) { while (len--) { dest[len] = (uint8_t)n; n >>= 8; diff --git a/software/src/common.h b/software/src/common.h index 8fac65be..f66f87ed 100644 --- a/software/src/common.h +++ b/software/src/common.h @@ -1,7 +1,7 @@ #ifndef COMMON_H__ #define COMMON_H__ -uint64_t atoui(const char* str); -void num_to_bytes(uint64_t n, uint32_t len, uint8_t* dest); +uint64_t atoui(const char *str); +void num_to_bytes(uint64_t n, uint32_t len, uint8_t *dest); #endif diff --git a/software/src/nested_util.c b/software/src/nested_util.c index 7d214238..801a2799 100644 --- a/software/src/nested_util.c +++ b/software/src/nested_util.c @@ -25,10 +25,10 @@ typedef struct { } countKeys; typedef struct { - NtpKs1* pNK; + NtpKs1 *pNK; uint32_t authuid; - uint64_t* keys; + uint64_t *keys; uint32_t keyCount; uint32_t startPos; @@ -36,20 +36,20 @@ typedef struct { } RecPar; -int compar_int(const void* a, const void* b) { - return (*(uint64_t*)b - *(uint64_t*)a); +int compar_int(const void *a, const void *b) { + return (*(uint64_t *)b - * (uint64_t *)a); } // Compare countKeys structure -int compar_special_int(const void* a, const void* b) { - return (((countKeys*)b)->count - ((countKeys*)a)->count); +int compar_special_int(const void *a, const void *b) { + return (((countKeys *)b)->count - ((countKeys *)a)->count); } // keys qsort and unique. -countKeys* uniqsort(uint64_t* possibleKeys, uint32_t size) { +countKeys *uniqsort(uint64_t *possibleKeys, uint32_t size) { unsigned int i, j = 0; int count = 0; - countKeys* our_counts; + countKeys *our_counts; qsort(possibleKeys, size, sizeof(uint64_t), compar_int); @@ -62,8 +62,7 @@ countKeys* uniqsort(uint64_t* possibleKeys, uint32_t size) { for (i = 0; i < size; i++) { if (possibleKeys[i + 1] == possibleKeys[i]) { count++; - } - else { + } else { our_counts[j].key = possibleKeys[i]; our_counts[j].count = count; j++; @@ -75,8 +74,8 @@ countKeys* uniqsort(uint64_t* possibleKeys, uint32_t size) { } // nested decrypt -static void nested_revover(RecPar* rp) { - struct Crypto1State* revstate, * revstate_start = NULL; +static void nested_revover(RecPar *rp) { + struct Crypto1State *revstate, * revstate_start = NULL; uint64_t lfsr = 0; uint32_t i, kcount = 0; @@ -98,14 +97,14 @@ static void nested_revover(RecPar* rp) { if (((kcount % MEM_CHUNK) == 0) || (kcount >= rp->keyCount)) { rp->keyCount += MEM_CHUNK; // printf("New chunk by %d, sizeof %lu\n", kcount, key_count * sizeof(uint64_t)); - void* tmp = realloc(rp->keys, rp->keyCount * sizeof(uint64_t)); + void *tmp = realloc(rp->keys, rp->keyCount * sizeof(uint64_t)); if (tmp == NULL) { printf("Memory allocation error for pk->possibleKeys"); // exit(EXIT_FAILURE); rp->keyCount = 0; return; } - rp->keys = (uint64_t*)tmp; + rp->keys = (uint64_t *)tmp; } rp->keys[kcount] = lfsr; kcount++; @@ -117,7 +116,7 @@ static void nested_revover(RecPar* rp) { // Truncate if (kcount != 0) { rp->keyCount = --kcount; - void* tmp = (uint64_t*)realloc(rp->keys, rp->keyCount * sizeof(uint64_t)); + void *tmp = (uint64_t *)realloc(rp->keys, rp->keyCount * sizeof(uint64_t)); if (tmp == NULL) { printf("Memory allocation error for pk->possibleKeys"); // exit(EXIT_FAILURE); @@ -131,11 +130,11 @@ static void nested_revover(RecPar* rp) { return; } -uint64_t* nested(NtpKs1* pNK, uint32_t sizePNK, uint32_t authuid, uint32_t* keyCount) { +uint64_t *nested(NtpKs1 *pNK, uint32_t sizePNK, uint32_t authuid, uint32_t *keyCount) { *keyCount = 0; uint32_t i; - RecPar* pRPs = malloc(sizeof(RecPar)); + RecPar *pRPs = malloc(sizeof(RecPar)); if (pRPs == NULL) { return NULL; } @@ -149,7 +148,7 @@ uint64_t* nested(NtpKs1* pNK, uint32_t sizePNK, uint32_t authuid, uint32_t* keyC nested_revover(pRPs); *keyCount = pRPs->keyCount; - uint64_t* keys = NULL; + uint64_t *keys = NULL; if (*keyCount != 0) { keys = malloc(*keyCount * sizeof(uint64_t)); if (keys != NULL) { @@ -159,9 +158,9 @@ uint64_t* nested(NtpKs1* pNK, uint32_t sizePNK, uint32_t authuid, uint32_t* keyC } free(pRPs); - countKeys* ck = uniqsort(keys, *keyCount); + countKeys *ck = uniqsort(keys, *keyCount); free(keys); - keys = (uint64_t*)NULL; + keys = (uint64_t *)NULL; *keyCount = 0; if (ck != NULL) { @@ -170,30 +169,28 @@ uint64_t* nested(NtpKs1* pNK, uint32_t sizePNK, uint32_t authuid, uint32_t* keyC // This key can be found here two or more times if (ck[i].count > 0) { *keyCount += 1; - void* tmp = realloc(keys, sizeof(uint64_t) * (*keyCount)); + void *tmp = realloc(keys, sizeof(uint64_t) * (*keyCount)); if (tmp != NULL) { keys = tmp; keys[*keyCount - 1] = ck[i].key; - } - else { + } else { printf("Cannot allocate memory for keys on merge."); free(keys); break; } } } - } - else { + } else { printf("Cannot allocate memory for ck on uniqsort."); } return keys; } // Return 1 if the nonce is invalid else return 0 -uint8_t valid_nonce(uint32_t Nt, uint32_t NtEnc, uint32_t Ks1, uint8_t* parity) { +uint8_t valid_nonce(uint32_t Nt, uint32_t NtEnc, uint32_t Ks1, uint8_t *parity) { return ( - (oddparity8((Nt >> 24) & 0xFF) == ((parity[0]) ^ oddparity8((NtEnc >> 24) & 0xFF) ^ BIT(Ks1, 16))) && \ - (oddparity8((Nt >> 16) & 0xFF) == ((parity[1]) ^ oddparity8((NtEnc >> 16) & 0xFF) ^ BIT(Ks1, 8))) && \ - (oddparity8((Nt >> 8) & 0xFF) == ((parity[2]) ^ oddparity8((NtEnc >> 8) & 0xFF) ^ BIT(Ks1, 0))) - ) ? 1 : 0; + (oddparity8((Nt >> 24) & 0xFF) == ((parity[0]) ^ oddparity8((NtEnc >> 24) & 0xFF) ^ BIT(Ks1, 16))) && \ + (oddparity8((Nt >> 16) & 0xFF) == ((parity[1]) ^ oddparity8((NtEnc >> 16) & 0xFF) ^ BIT(Ks1, 8))) && \ + (oddparity8((Nt >> 8) & 0xFF) == ((parity[2]) ^ oddparity8((NtEnc >> 8) & 0xFF) ^ BIT(Ks1, 0))) + ) ? 1 : 0; } diff --git a/software/src/nested_util.h b/software/src/nested_util.h index 12699ac4..2d569ec7 100644 --- a/software/src/nested_util.h +++ b/software/src/nested_util.h @@ -8,7 +8,7 @@ typedef struct { uint32_t ks1; } NtpKs1; -uint8_t valid_nonce(uint32_t Nt, uint32_t NtEnc, uint32_t Ks1, uint8_t* parity); -uint64_t* nested(NtpKs1* pNK, uint32_t sizePNK, uint32_t authuid, uint32_t* keyCount); +uint8_t valid_nonce(uint32_t Nt, uint32_t NtEnc, uint32_t Ks1, uint8_t *parity); +uint64_t *nested(NtpKs1 *pNK, uint32_t sizePNK, uint32_t authuid, uint32_t *keyCount); -#endif \ No newline at end of file +#endif diff --git a/software/src/staticnested.c b/software/src/staticnested.c index 094249c6..f857bfd2 100644 --- a/software/src/staticnested.c +++ b/software/src/staticnested.c @@ -30,11 +30,9 @@ int main(int argc, char *const argv[]) { // We found that the gen2 tag is vulnerable too but parameter must be adapted depending on the attacked key if (type == 0x61) { dist = 161; - } - else if (type == 0x60) { + } else if (type == 0x60) { dist = 160; - } - else { + } else { // can't be here!!! goto error; } @@ -50,7 +48,7 @@ int main(int argc, char *const argv[]) { ++j; dist += 160; - void* tmp = realloc(pNK, sizeof(NtpKs1) * j); + void *tmp = realloc(pNK, sizeof(NtpKs1) * j); if (tmp == NULL) { goto error; } From ad3241aeb90b420788e6e75b0fed43c59fb5f352 Mon Sep 17 00:00:00 2001 From: dxl <64101226@qq.com> Date: Wed, 20 Sep 2023 11:00:43 +0800 Subject: [PATCH 18/38] doc --- docs/images/cli_staticnested.jpg | Bin 0 -> 11544 bytes docs/technical_whitepaper.md | 223 +++++++++++++++++-------------- 2 files changed, 121 insertions(+), 102 deletions(-) create mode 100644 docs/images/cli_staticnested.jpg diff --git a/docs/images/cli_staticnested.jpg b/docs/images/cli_staticnested.jpg new file mode 100644 index 0000000000000000000000000000000000000000..05272b469341cfc583f551557975c6adc59d3f47 GIT binary patch literal 11544 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

;e)-?r5Yd`6X;WFS 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 literal 0 HcmV?d00001 diff --git a/docs/technical_whitepaper.md b/docs/technical_whitepaper.md index 308b6d6f..527fcf9a 100644 --- a/docs/technical_whitepaper.md +++ b/docs/technical_whitepaper.md @@ -5,58 +5,67 @@ # ChameleonUltra Why not keep using ATXMEGA128? -First of all, it is difficult to buy chips because the lead time for the main chip is too long, and because the price has skyrocketed. Secondly, because the interaction speed of the ATXMEGA, emulation is slow, the decryption performance of the READER mode cannot meet the needs, and the LF support cannot be added, so we have been trying to upgrade it, such as using the latest ARM to replace the AVR framework, and the performance will definitely be greatly improved. +First of all, it is difficult to buy chips because the lead time for the main chip is too long, and because the price +has skyrocketed. Secondly, because the interaction speed of the ATXMEGA, emulation is slow, the decryption performance +of the READER mode cannot meet the needs, and the LF support cannot be added, so we have been trying to upgrade it, such +as using the latest ARM to replace the AVR framework, and the performance will definitely be greatly improved. # Why nRF52840? -NRF52840 has a built-in NFC Tag-A module, but no one seems to care about it. After playing with HydraNFC's TRF7970A and FlipperZero's ST25R3916, the developers found that they can only emulate MIFARE Classic with a very high FDT. -We accidentally tested the NFC of nRF52840, and found that it is not only surprisingly easy to emulate a complete MIFARE Classic card, but also has very good emulation performance, friendly data flow interaction, and very fast response, unlike the former which is limited by the SPI bus clock rate. We also found that it has ultra-low power consumption, ultra-small size, 256kb/1M large RAM and Flash, also has BLE5.0 and USB2.0 FS, super CortexM4F, most importantly, it is very cheap! This is undoubtedly a treasure discovery for us! +NRF52840 has a built-in NFC Tag-A module, but no one seems to care about it. After playing with HydraNFC's TRF7970A and +FlipperZero's ST25R3916, the developers found that they can only emulate MIFARE Classic with a very high FDT. +We accidentally tested the NFC of nRF52840, and found that it is not only surprisingly easy to emulate a complete MIFARE +Classic card, but also has very good emulation performance, friendly data flow interaction, and very fast response, +unlike the former which is limited by the SPI bus clock rate. We also found that it has ultra-low power consumption, +ultra-small size, 256kb/1M large RAM and Flash, also has BLE5.0 and USB2.0 FS, super CortexM4F, most importantly, it is +very cheap! This is undoubtedly a treasure discovery for us! -Below we will explain in detail how we exploited the performance of the NRF52840, and what seemingly impossible functions have been realized with it! +Below we will explain in detail how we exploited the performance of the NRF52840, and what seemingly impossible +functions have been realized with it! # Supported functions ## High Frequency Attack -| Attack Type | Tag Type | Whether the hardware supports | Does the software support | Whether the application layer supports | Note | -|--------------|:-------------:|------------------------------:|---------------------------|:--------------------------------------:|------------------------:| -| Sniffing | No | No | No | No | | +| Attack Type | Tag Type | Whether the hardware supports | Does the software support | Whether the application layer supports | Note | +|--------------|:--------------:|------------------------------:|---------------------------|:--------------------------------------:|-------------------------:| +| Sniffing | No | No | No | No | | | MFKEY32 V2 | MIFARE Classic | Support | Support | Support | MIFARE Classic Detection | -| Darkside | MIFARE Classic | Support | Support | Support | Encrypted 4 bit NAck | -| Nested | MIFARE Classic | Support | Support | Support | PRNG(Distance guess) | -| StaticNested | MIFARE Classic | Support | Support | Not yet implemented | PRNG(2NT Fast Decrypt) | -| HardNested | MIFARE Classic | Support | Support | Not yet implemented | No | -| Relay attack | ISO14443A | Support | Support | Not yet implemented | No | +| Darkside | MIFARE Classic | Support | Support | Support | Encrypted 4 bit NAck | +| Nested | MIFARE Classic | Support | Support | Support | PRNG(Distance guess) | +| StaticNested | MIFARE Classic | Support | Support | Not yet implemented | PRNG(2NT Fast Decrypt) | +| HardNested | MIFARE Classic | Support | Support | Not yet implemented | No | +| Relay attack | ISO14443A | Support | Support | Not yet implemented | No | ## High Frequency emulation -| Card Type | Encoding Type | Whether the hardware supports | Does the software support | Whether the application layer supports | Note | -|-------------------------------|:--------------------:|------------------------------:|---------------------------|:--------------------------------------:|-----------------------------------------:| -| Other than ISO14443A | No | No | No | No | [NRF52 NFC Module][nrf52_nfc_module_doc] | -| NTAG 21x (210-218) | ISO14443A/106 kbit/s | Support | Support | Not yet implemented | | -| MIFARE Ultralight | ISO14443A/106 kbit/s | Support | Support | Not yet implemented | | -| MIFARE Ultralight Ev1 | ISO14443A/106 kbit/s | Support | Support | Not yet implemented | | -| MIFARE Ultralight C | ISO14443A/106 kbit/s | Support | Support | Not yet implemented | | +| Card Type | Encoding Type | Whether the hardware supports | Does the software support | Whether the application layer supports | Note | +|--------------------------------|:--------------------:|------------------------------:|---------------------------|:--------------------------------------:|-----------------------------------------:| +| Other than ISO14443A | No | No | No | No | [NRF52 NFC Module][nrf52_nfc_module_doc] | +| NTAG 21x (210-218) | ISO14443A/106 kbit/s | Support | Support | Not yet implemented | | +| MIFARE Ultralight | ISO14443A/106 kbit/s | Support | Support | Not yet implemented | | +| MIFARE Ultralight Ev1 | ISO14443A/106 kbit/s | Support | Support | Not yet implemented | | +| MIFARE Ultralight C | ISO14443A/106 kbit/s | Support | Support | Not yet implemented | | | MIFARE Classic1K/2K/4K (4B/7B) | ISO14443A/106 kbit/s | Support | Support | Support | | -| MIFARE DESFire | ISO14443A High Rate | Only supported Low rate | Only supported Low rate | Not yet implemented | | -| MIFARE DESFire EV1 | ISO14443A High rate | Only supported Low rate | Only supported Low rate | Not yet implemented | Backward compatible | -| MIFARE DESFire EV2 | ISO14443A High rate | Only supported Low rate | Only supported Low rate | Not yet implemented | | -| MIFARE Plus | ISO14443A High rate | Only supported Low rate | Only supported Low rate | Not yet implemented | | +| MIFARE DESFire | ISO14443A High Rate | Only supported Low rate | Only supported Low rate | Not yet implemented | | +| MIFARE DESFire EV1 | ISO14443A High rate | Only supported Low rate | Only supported Low rate | Not yet implemented | Backward compatible | +| MIFARE DESFire EV2 | ISO14443A High rate | Only supported Low rate | Only supported Low rate | Not yet implemented | | +| MIFARE Plus | ISO14443A High rate | Only supported Low rate | Only supported Low rate | Not yet implemented | | ## High Frequency Reader -| Card Type | Encoding Type | Whether the hardware supports | Does the software support | Whether the application layer supports | Note | -|-------------------------------|:--------------------:|---------------------------------------------:|----------------------------------------------|:--------------------------------------:|-------------------------------------------:| -| Non <13.56MHz or ISO14443A> | No | No | No | No | [NXP RC522 Datasheet][nxp_rc522_datasheet] | -| NTAG 21x (210-218) | ISO14443A/106 kbit/s | Support | Support | Not yet implemented | | -| MIFARE Ultralight | ISO14443A/106 kbit/s | Support | Support | Not yet implemented | | -| MIFARE Ultralight Ev1 | ISO14443A/106 kbit/s | Support | Support | Not yet implemented | | -| MIFARE Ultralight C | ISO14443A/106 kbit/s | Support | Support | Not yet implemented | | +| Card Type | Encoding Type | Whether the hardware supports | Does the software support | Whether the application layer supports | Note | +|---------------------------------|:--------------------:|---------------------------------------------:|----------------------------------------------|:--------------------------------------:|-------------------------------------------:| +| Non <13.56MHz or ISO14443A> | No | No | No | No | [NXP RC522 Datasheet][nxp_rc522_datasheet] | +| NTAG 21x (210-218) | ISO14443A/106 kbit/s | Support | Support | Not yet implemented | | +| MIFARE Ultralight | ISO14443A/106 kbit/s | Support | Support | Not yet implemented | | +| MIFARE Ultralight Ev1 | ISO14443A/106 kbit/s | Support | Support | Not yet implemented | | +| MIFARE Ultralight C | ISO14443A/106 kbit/s | Support | Support | Not yet implemented | | | MIFARE Classic 1K/2K/4K (4B/7B) | ISO14443A/106 kbit/s | Support | Support | Support | | -| MIFARE DESFire | ISO14443A High Rate | Supports low rates, or possibly higher rates | Supports low rates, or possibly higher rates | Not yet implemented | | -| MIFARE DESFire EV1 | ISO14443A High rate | Supports low rates, or possibly higher rates | Supports low rates, or possibly higher rates | Not yet implemented | Backward compatible | -| MIFARE DESFire EV2 | ISO14443A High rate | Supports low rates, or possibly higher rates | Supports low rates, or possibly higher rates | Not yet implemented | | -| MIFARE Plus | ISO14443A High rate | Supports low rates, or possibly higher rates | Supports low rates, or possibly higher rates | Not yet implemented | | +| MIFARE DESFire | ISO14443A High Rate | Supports low rates, or possibly higher rates | Supports low rates, or possibly higher rates | Not yet implemented | | +| MIFARE DESFire EV1 | ISO14443A High rate | Supports low rates, or possibly higher rates | Supports low rates, or possibly higher rates | Not yet implemented | Backward compatible | +| MIFARE DESFire EV2 | ISO14443A High rate | Supports low rates, or possibly higher rates | Supports low rates, or possibly higher rates | Not yet implemented | | +| MIFARE Plus | ISO14443A High rate | Supports low rates, or possibly higher rates | Supports low rates, or possibly higher rates | Not yet implemented | | ## Low Frequency Attack @@ -67,103 +76,113 @@ Below we will explain in detail how we exploited the performance of the NRF52840 ## Low Frequency emulation -| Card Type | Encoding Type | Whether the hardware supports | Does the software support | Whether the application layer supports | Note | -|--------------------------|:-------------:|------------------------------:|---------------------------|:--------------------------------------:|----------------------------------------------:| +| Card Type | Encoding Type | Whether the hardware supports | Does the software support | Whether the application layer supports | Note | +|---------------------------------|:-------------:|------------------------------:|---------------------------|:--------------------------------------:|----------------------------------------------:| | Other than <125KHz/ASK/PSK/FSK> | No | No | No | No | Only 125 khz RF, Modulation ASK, FSK and PSK. | -| EM410x | ASK | Support | Support | Support | EM4100 is support(AD 64bit) | -| T5577 | ASK | Support | Support | Not yet implemented | | -| EM4305 | ASK | Support | Support | Not yet implemented | | -| HID Prox | FSK | Support | Support | Not yet implemented | | -| Indala | PSK | Support | Support | Not yet implemented | | -| FDX-B | ASK | Support | Support | Not yet implemented | | -| Paradox | FSK | Support | Support | Not yet implemented | | -| Keri | PSK | Support | Support | Not yet implemented | | -| AWD | FSK | Support | Support | Not yet implemented | | -| ioProx | FSK | Support | Support | Not yet implemented | | -| securakey | ASK | Support | Support | Not yet implemented | | -| gallagher | ASK | Support | Support | Not yet implemented | | -| PAC/Stanley | ASK | Support | Support | Not yet implemented | | -| Presco | ASK | Support | Support | Not yet implemented | | -| Visa2000 | ASK | Support | Support | Not yet implemented | | -| Viking | ASK | Support | Support | Not yet implemented | | -| Noralsy | ASK | Support | Support | Not yet implemented | | -| NexWatch | PSK | Support | Support | Not yet implemented | | -| Jablotron | ASK | Support | Support | Not yet implemented | | +| EM410x | ASK | Support | Support | Support | EM4100 is support(AD 64bit) | +| T5577 | ASK | Support | Support | Not yet implemented | | +| EM4305 | ASK | Support | Support | Not yet implemented | | +| HID Prox | FSK | Support | Support | Not yet implemented | | +| Indala | PSK | Support | Support | Not yet implemented | | +| FDX-B | ASK | Support | Support | Not yet implemented | | +| Paradox | FSK | Support | Support | Not yet implemented | | +| Keri | PSK | Support | Support | Not yet implemented | | +| AWD | FSK | Support | Support | Not yet implemented | | +| ioProx | FSK | Support | Support | Not yet implemented | | +| securakey | ASK | Support | Support | Not yet implemented | | +| gallagher | ASK | Support | Support | Not yet implemented | | +| PAC/Stanley | ASK | Support | Support | Not yet implemented | | +| Presco | ASK | Support | Support | Not yet implemented | | +| Visa2000 | ASK | Support | Support | Not yet implemented | | +| Viking | ASK | Support | Support | Not yet implemented | | +| Noralsy | ASK | Support | Support | Not yet implemented | | +| NexWatch | PSK | Support | Support | Not yet implemented | | +| Jablotron | ASK | Support | Support | Not yet implemented | | ## Low Frequency Reader -| Card Type | Encoding Type | Whether the hardware supports | Does the software support | Whether the application layer supports | Note | -|--------------------------|:-------------:|------------------------------:|---------------------------|:--------------------------------------:|----------------------------------------------:| +| Card Type | Encoding Type | Whether the hardware supports | Does the software support | Whether the application layer supports | Note | +|---------------------------------|:-------------:|------------------------------:|---------------------------|:--------------------------------------:|----------------------------------------------:| | Other than <125KHz/ASK/PSK/FSK> | No | No | No | No | Only 125 khz RF, Modulation ASK, FSK and PSK. | -| EM410x | ASK | Support | Support | Support | | -| T5577 | ASK | Support | Support | Support(Write) | | -| EM4305 | ASK | Support | Support | Not yet implemented | | -| HID Prox | FSK | Support | Support | Not yet implemented | | -| Indala | PSK | Support | Support | Not yet implemented | | -| FDX-B | ASK | Support | Support | Not yet implemented | | -| Paradox | FSK | Support | Support | Not yet implemented | | -| Keri | PSK | Support | Support | Not yet implemented | | -| AWD | FSK | Support | Support | Not yet implemented | | -| ioProx | FSK | Support | Support | Not yet implemented | | -| securakey | ASK | Support | Support | Not yet implemented | | -| gallagher | ASK | Support | Support | Not yet implemented | | -| PAC/Stanley | ASK | Support | Support | Not yet implemented | | -| Presco | ASK | Support | Support | Not yet implemented | | -| Visa2000 | ASK | Support | Support | Not yet implemented | | -| Viking | ASK | Support | Support | Not yet implemented | | -| Noralsy | ASK | Support | Support | Not yet implemented | | -| NexWatch | PSK | Support | Support | Not yet implemented | | -| Jablotron | ASK | Support | Support | Not yet implemented | | +| EM410x | ASK | Support | Support | Support | | +| T5577 | ASK | Support | Support | Support(Write) | | +| EM4305 | ASK | Support | Support | Not yet implemented | | +| HID Prox | FSK | Support | Support | Not yet implemented | | +| Indala | PSK | Support | Support | Not yet implemented | | +| FDX-B | ASK | Support | Support | Not yet implemented | | +| Paradox | FSK | Support | Support | Not yet implemented | | +| Keri | PSK | Support | Support | Not yet implemented | | +| AWD | FSK | Support | Support | Not yet implemented | | +| ioProx | FSK | Support | Support | Not yet implemented | | +| securakey | ASK | Support | Support | Not yet implemented | | +| gallagher | ASK | Support | Support | Not yet implemented | | +| PAC/Stanley | ASK | Support | Support | Not yet implemented | | +| Presco | ASK | Support | Support | Not yet implemented | | +| Visa2000 | ASK | Support | Support | Not yet implemented | | +| Viking | ASK | Support | Support | Not yet implemented | | +| Noralsy | ASK | Support | Support | Not yet implemented | | +| NexWatch | PSK | Support | Support | Not yet implemented | | +| Jablotron | ASK | Support | Support | Not yet implemented | | ## Low Frequency Modulation - -| Modulation Type | wav | -|-----------------|----------------------------:| +| Modulation Type | wav | +|-----------------|------------------------------------:| | PSK | ![PSK WAV](images/measured-psk.png) | | FSK | ![FSK WAV](images/measured-fsk.png) | | ASK | ![ASK WAV](images/measured-ask.png) | # Ultra-low power consumption -It integrates a high-performance and low-power NFC module inside. When the NFC unit is turned on, the total current of the chip is only 5mA@3.3V. +It integrates a high-performance and low-power NFC module inside. When the NFC unit is turned on, the total current of +the chip is only 5mA@3.3V. The underlying interaction is done independently by the NFC unit and does not occupy the CPU. -In addition, the nRF52840 itself is a high-performance low-power BLE chip, and the encryption and calculation process is only 7mA@3.3V. It can greatly reduce the battery volume and prolong the working time. That is to say, the 35mAh 10mm*40mm button lithium battery can guarantee to be charged once every half a year under the working condition of swiping the card 8 times a day for 3 seconds each time. Full potential for everyday use. +In addition, the nRF52840 itself is a high-performance low-power BLE chip, and the encryption and calculation process is +only 7mA@3.3V. It can greatly reduce the battery volume and prolong the working time. That is to say, the 35mAh 10mm* +40mm button lithium battery can guarantee to be charged once every half a year under the working condition of swiping +the card 8 times a day for 3 seconds each time. Full potential for everyday use. # Not just UID, but a real and complete MIFARE Classic emulation -We can easily and completely emulate all data and password verification of all sectors, and can customize SAK, ATQA, ATS, etc. Similar to an open CPU card development platform, 14A interaction of various architectures can be easily realized. +We can easily and completely emulate all data and password verification of all sectors, and can customize SAK, ATQA, +ATS, etc. Similar to an open CPU card development platform, 14A interaction of various architectures can be easily +realized. # Super compatibility with low-power locks using batteries -The structure of the old Chameleon AVR is slow to start during emulation. Faced with a battery-powered low-power lock and an integrated lock on the door, it will be frequently interrupted, and the verification interaction cannot be completed completely, resulting in no response when swiping the card. +The structure of the old Chameleon AVR is slow to start during emulation. Faced with a battery-powered low-power lock +and an integrated lock on the door, it will be frequently interrupted, and the verification interaction cannot be +completed completely, resulting in no response when swiping the card. -In order to reduce power consumption, the battery lock will send out a field signal as short as possible when searching for a card, which is no problem for the original card, but it is fatal for the MCU emulated card. Cards or mobile smart bracelets emulated by the MCU cannot wake up and respond in such a short time, so many battery locks cannot open the door, which greatly reduces the user experience. +In order to reduce power consumption, the battery lock will send out a field signal as short as possible when searching +for a card, which is no problem for the original card, but it is fatal for the MCU emulated card. Cards or mobile smart +bracelets emulated by the MCU cannot wake up and respond in such a short time, so many battery locks cannot open the +door, which greatly reduces the user experience. -This project specially optimizes the start-up and interaction logic and antenna for low-power reading heads. After testing a variety of common low-power reading heads, they can open the door perfectly by swiping the card. +This project specially optimizes the start-up and interaction logic and antenna for low-power reading heads. After +testing a variety of common low-power reading heads, they can open the door perfectly by swiping the card. # Ultra-fast response speed and low interaction delay(MIFARE Classic) -| Tag/Emulation | FDT | "**_FDT_**" Rating | -|----------------------|:---------------------------:|:--------------------------------------------------------------------------------:| -| Standard MIFARE Card | ![Standard_m1_s50](images/fdt_standard_s50.png) | ⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐ | -| Chameleon Ultra | ![Chameleon Ultra](images/fdt_chameleon_ultra.png) | ⭐⭐⭐⭐⭐⭐⭐⭐ | -| Proxmark3 Rdv4.01 | ![Proxmark3_Rdv4_RRG_(Firmware build at 20201026)](images/fdt_pm3_rdv401.png) | ⭐⭐⭐⭐ | -| RedMi K30 | ![Xiaomi_k30u_smartkey](images/fdt_redmi_k30.png) | ⭐⭐⭐⭐⭐⭐ | -| Chameleon Tiny | ![Chameleon Tiny](images/fdt_chameleon_tiny.png) | ⭐⭐⭐⭐⭐ | -| Flipper Zero | ![Flipper Zero](images/fdt_flipper_zero.png) | ⭐⭐ | +| Tag/Emulation | FDT | "**_FDT_**" Rating | +|----------------------|:-----------------------------------------------------------------------------:|:--------------------------------------------------------------------------------:| +| Standard MIFARE Card | ![Standard_m1_s50](images/fdt_standard_s50.png) | ⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐ | +| Chameleon Ultra | ![Chameleon Ultra](images/fdt_chameleon_ultra.png) | ⭐⭐⭐⭐⭐⭐⭐⭐ | +| Proxmark3 Rdv4.01 | ![Proxmark3_Rdv4_RRG_(Firmware build at 20201026)](images/fdt_pm3_rdv401.png) | ⭐⭐⭐⭐ | +| RedMi K30 | ![Xiaomi_k30u_smartkey](images/fdt_redmi_k30.png) | ⭐⭐⭐⭐⭐⭐ | +| Chameleon Tiny | ![Chameleon Tiny](images/fdt_chameleon_tiny.png) | ⭐⭐⭐⭐⭐ | +| Flipper Zero | ![Flipper Zero](images/fdt_flipper_zero.png) | ⭐⭐ | # 256kB super large RAM cooperates with RC522 to enable attacks - -| Attack Type | CLI | -|--------------|:------------------------------:| -| MFKEY32 V2 | ![attack_MIFARE_mfkey32](images/cli_mfkey32v2.png) | -| Darkside | ![attack_MIFARE_darkside](images/cli_darkside.png) | -| Nested | ![attack_MIFARE_nested](images/cli_nested.png) | -| StaticNested | Coming Soon | -| HardNested | Coming Soon | -| Relay attack | Coming Soon | +| Attack Type | CLI | +|--------------|:----------------------------------------------------------:| +| MFKEY32 V2 | ![attack_MIFARE_mfkey32](images/cli_mfkey32v2.png) | +| Darkside | ![attack_MIFARE_darkside](images/cli_darkside.png) | +| Nested | ![attack_MIFARE_nested](images/cli_nested.png) | +| StaticNested | ![attack_MIFARE_staticnested](images/cli_staticnested.jpg) | +| HardNested | Coming Soon | +| Relay attack | Coming Soon | # Hardware frame diagram From 86c1a71f7cdcd53eceb21833cfca6b6d3d4be781 Mon Sep 17 00:00:00 2001 From: dxl <64101226@qq.com> Date: Wed, 20 Sep 2023 11:14:06 +0800 Subject: [PATCH 19/38] Update the table of MFC attack support levels. --- docs/technical_whitepaper.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/technical_whitepaper.md b/docs/technical_whitepaper.md index 527fcf9a..bf6f887d 100644 --- a/docs/technical_whitepaper.md +++ b/docs/technical_whitepaper.md @@ -33,7 +33,7 @@ functions have been realized with it! | MFKEY32 V2 | MIFARE Classic | Support | Support | Support | MIFARE Classic Detection | | Darkside | MIFARE Classic | Support | Support | Support | Encrypted 4 bit NAck | | Nested | MIFARE Classic | Support | Support | Support | PRNG(Distance guess) | -| StaticNested | MIFARE Classic | Support | Support | Not yet implemented | PRNG(2NT Fast Decrypt) | +| StaticNested | MIFARE Classic | Support | Support | Support | PRNG(2NT Fast Decrypt) | | HardNested | MIFARE Classic | Support | Support | Not yet implemented | No | | Relay attack | ISO14443A | Support | Support | Not yet implemented | No | From 499fa35c25d03d915aa53de1938de5097ac1be16 Mon Sep 17 00:00:00 2001 From: dxl <64101226@qq.com> Date: Thu, 21 Sep 2023 18:28:21 +0800 Subject: [PATCH 20/38] Fixed initialization bugs and added raw command implementation functions. --- .../application/src/rfid/reader/hf/rc522.c | 147 +++++++++++++++++- .../application/src/rfid/reader/hf/rc522.h | 3 + 2 files changed, 144 insertions(+), 6 deletions(-) diff --git a/firmware/application/src/rfid/reader/hf/rc522.c b/firmware/application/src/rfid/reader/hf/rc522.c index be7d2375..1bec25cc 100644 --- a/firmware/application/src/rfid/reader/hf/rc522.c +++ b/firmware/application/src/rfid/reader/hf/rc522.c @@ -196,9 +196,11 @@ void pcd_14a_reader_reset(void) { write_register_single(CommandReg, PCD_IDLE); write_register_single(CommandReg, PCD_RESET); - // Switch antenna - clear_register_mask(TxControlReg, 0x03); - set_register_mask(TxControlReg, 0x03); + bsp_delay_ms(10); + + // Then default does not allow the antenna + // Please don't continue to make high -frequency antennas + pcd_14a_reader_antenna_off(); // Disable the timer of 522, use the MCU timer timeout time write_register_single(TModeReg, 0x00); @@ -208,9 +210,7 @@ void pcd_14a_reader_reset(void) { // Define common mode and receive common mode and receiveMiFare cartoon communication, CRC initial value 0x6363 write_register_single(ModeReg, 0x3D); - // Then default does not allow the antenna - // Please don't continue to make high -frequency antennas - pcd_14a_reader_antenna_off(); + bsp_delay_ms(10); } } @@ -1122,3 +1122,138 @@ inline void crc_14a_append(uint8_t *pbtData, size_t szLen) { inline void pcd_14a_reader_crc_computer(uint8_t use522CalcCRC) { m_crc_computer = use522CalcCRC; } + +/** +* @brief : The hf 14a raw command implementation function can be used to send the 14A command with the specified configuration parameters. +* @param :waitResp : Wait for tag response +* @param :appendCrc : Do you want to add CRC before sending +* @param :bitsFrame : Is it necessary to send bit frames, which are mutually exclusive with the appendCrc parameter. +* @param :autoSelect : Automatically select card before sending data +* @param :keepField : Do you want to keep the RF field on after sending +* @param :checkCrc : Is CRC verified after receiving data? If CRC verification is enabled, CRC bytes will be automatically removed after verification is completed. +* @param :waitRespTimeout : If waitResp is enabled, this parameter will be the timeout value to wait for the tag to respond +* @param :szDataSend : The number of bytes or bits of data to be sent +* @param :pDataSend : Pointer to the buffer of the data to be sent +* +* @retval : Execution Status +* +*/ +uint8_t pcd_14a_reader_raw_cmd(bool openRFField, bool waitResp, bool appendCrc, bool bitsFrame, bool autoSelect, bool keepField, bool checkCrc, uint16_t waitRespTimeout, + uint16_t szDataSend, uint8_t *pDataSend, uint8_t *pDataRecv, uint16_t *pszDataRecv, uint16_t szDataRecvBitMax) { + // Status code, default is OK. + uint8_t status = HF_TAG_OK; + // Reset recv length. + *pszDataRecv = 0; + // Old response timeout. + uint16_t oldWaitRespTimeout; + + if (openRFField) { // Open rf filed? + pcd_14a_reader_antenna_on(); + bsp_delay_ms(8); + } + + // Is there any data that needs to be sent + if (szDataSend > 0) { + // If additional CRC is required, first add the CRC to the tail. + if (appendCrc) { + // Note: Adding CRC requires at least two bytes of free space. If the transmitted data is already greater than or equal to 64, an error needs to be returned + if (szDataSend > (DEF_FIFO_LENGTH - DEF_CRC_LENGTH)) { + NRF_LOG_INFO("Adding CRC requires data length less than or equal to 62."); + status = STATUS_PAR_ERR; + } else { + // Calculate and append CRC byte data to the buffer + crc_14a_append(pDataSend, szDataSend); + // CRC is also sent as part of the data, so the total length needs to be added to the CRC length here + szDataSend += DEF_CRC_LENGTH; + } + } + + // Ensure that the previous operation is normal + if (status == HF_TAG_OK) { + // Determine if card selection is necessary first based on needs + if (autoSelect) { + picc_14a_tag_t ti; + status = pcd_14a_reader_scan_once(&ti); + // Determine whether the card search was successful + if (status != HF_TAG_OK) { + return status; + } + } + + // If there is no need to receive data, the data receiving cache needs to be empty, otherwise a specified timeout value needs to be set + if (waitResp) { + // Caching old timeout values + oldWaitRespTimeout = g_com_timeout_ms; + // Then set the new values in + g_com_timeout_ms = waitRespTimeout; + } else { + pDataRecv = NULL; + } + + // Ensure that the previous operation is normal + if (status == HF_TAG_OK) { + if (bitsFrame) { + status = pcd_14a_reader_bits_transfer( + pDataSend, + szDataSend, + NULL, + pDataRecv, + NULL, + pszDataRecv, + szDataRecvBitMax + ); + } else { + status = pcd_14a_reader_bytes_transfer( + PCD_TRANSCEIVE, + pDataSend, + szDataSend, + pDataRecv, + pszDataRecv, + szDataRecvBitMax + ); + } + } + + // If we need to receive data, we need to perform further operations on the data based on the remaining configuration after receiving it + if (waitResp) { + // Number of bits to bytes + uint8_t finalRecvBytes = (*pszDataRecv / 8) + (*pszDataRecv % 8 > 0 ? 1 : 0); + // If CRC verification is required, we need to perform CRC calculation + if (checkCrc) { + if (finalRecvBytes >= 3) { // Ensure at least three bytes (one byte of data+two bytes of CRC) + // Calculate and store CRC + uint8_t crc_buff[DEF_CRC_LENGTH] = { 0x00 }; + crc_14a_calculate(pDataRecv, finalRecvBytes - DEF_CRC_LENGTH, crc_buff); + // Verify CRC + if (pDataRecv[finalRecvBytes - 2] != crc_buff[0] || pDataRecv[finalRecvBytes - 1] != crc_buff[1]) { + // We have found an error in CRC verification and need to inform the upper computer! + *pszDataRecv = 0; + status = HF_ERR_CRC; + } else { + // If the CRC needs to be verified by the device and the device determines that the CRC is normal, + // we will return the data without CRC + *pszDataRecv = finalRecvBytes - DEF_CRC_LENGTH; + } + } else { + // The data is insufficient to support the length of the CRC, so it is returned as is + *pszDataRecv = 0; + } + } else { + // Do not verify CRC, all data is returned as is + *pszDataRecv = finalRecvBytes; + } + // We need to recover the timeout value + g_com_timeout_ms = oldWaitRespTimeout; + } else { + *pszDataRecv = 0; + } + } + } + + // Finally, keep the field open as needed + if (!keepField) { + pcd_14a_reader_antenna_off(); + } + + return status; +} \ No newline at end of file diff --git a/firmware/application/src/rfid/reader/hf/rc522.h b/firmware/application/src/rfid/reader/hf/rc522.h index 9546aefc..11926ab3 100644 --- a/firmware/application/src/rfid/reader/hf/rc522.h +++ b/firmware/application/src/rfid/reader/hf/rc522.h @@ -220,6 +220,9 @@ uint8_t pcd_14a_reader_mf1_read(uint8_t addr, uint8_t *pData); uint8_t pcd_14a_reader_halt_tag(void); void pcd_14a_reader_fast_halt_tag(void); +uint8_t pcd_14a_reader_raw_cmd(bool openRFField, bool waitResp, bool appendCrc, bool bitsFrame, bool autoSelect, bool keepField, bool checkCrc, uint16_t waitRespTimeout, + uint16_t szDataSend, uint8_t* pDataSend, uint8_t* pDataRecv, uint16_t* pszDataRecv, uint16_t szDataRecvBitMax); + // UID & UFUID tag operation uint8_t pcd_14a_reader_gen1a_unlock(void); uint8_t pcd_14a_reader_gen1a_uplock(void); From 879b8e34592623dc33ec6666702a535ee11cfc36 Mon Sep 17 00:00:00 2001 From: dxl <64101226@qq.com> Date: Thu, 21 Sep 2023 18:30:05 +0800 Subject: [PATCH 21/38] Implemented hf 14a raw --- firmware/application/src/app_cmd.c | 99 ++++++++++++++++++++++----- firmware/application/src/data_cmd.h | 2 + software/script/chameleon_cli_unit.py | 59 ++++++++++++++++ software/script/chameleon_cmd.py | 99 ++++++++++++++++++++++++++- 4 files changed, 239 insertions(+), 20 deletions(-) diff --git a/firmware/application/src/app_cmd.c b/firmware/application/src/app_cmd.c index 93518efc..3df5071b 100644 --- a/firmware/application/src/app_cmd.c +++ b/firmware/application/src/app_cmd.c @@ -256,6 +256,30 @@ static data_frame_tx_t *cmd_processor_mf1_detect_prng(uint16_t cmd, uint16_t sta return data_frame_make(cmd, HF_TAG_OK, sizeof(type), &type); } +// We have a reusable payload structure. +typedef struct { + uint8_t type_known; + uint8_t block_known; + uint8_t key_known[6]; + uint8_t type_target; + uint8_t block_target; +} PACKED nested_common_payload_t; + +static data_frame_tx_t *cmd_processor_mf1_static_nested_acquire(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + mf1_static_nested_core_t sncs; + if (length != sizeof(nested_common_payload_t)) { + return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); + } + + nested_common_payload_t *payload = (nested_common_payload_t *)data; + status = static_nested_recover_key(bytes_to_num(payload->key_known, 6), payload->block_known, payload->type_known, payload->block_target, payload->type_target, &sncs); + if (status != HF_TAG_OK) { + return data_frame_make(cmd, status, 0, NULL); + } + // mf1_static_nested_core_t is PACKED and comprises only bytes so we can use it directly + return data_frame_make(cmd, HF_TAG_OK, sizeof(sncs), (uint8_t *)(&sncs)); +} + static data_frame_tx_t *cmd_processor_mf1_darkside_acquire(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { if (length != 4) { return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); @@ -300,15 +324,6 @@ static data_frame_tx_t *cmd_processor_mf1_detect_nt_dist(uint16_t cmd, uint16_t return data_frame_make(cmd, HF_TAG_OK, sizeof(payload_resp), (uint8_t *)&payload_resp); } -// We have a reusable payload structure. -typedef struct { - uint8_t type_known; - uint8_t block_known; - uint8_t key_known[6]; - uint8_t type_target; - uint8_t block_target; -} PACKED nested_common_payload_t; - static data_frame_tx_t *cmd_processor_mf1_nested_acquire(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { mf1_nested_core_t ncs[SETS_NR]; if (length != sizeof(nested_common_payload_t)) { @@ -383,19 +398,64 @@ static data_frame_tx_t *cmd_processor_mf1_write_one_block(uint16_t cmd, uint16_t return data_frame_make(cmd, status, 0, NULL); } -static data_frame_tx_t *cmd_processor_mf1_static_nested_acquire(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - mf1_static_nested_core_t sncs; - if (length != sizeof(nested_common_payload_t)) { +static data_frame_tx_t *cmd_processor_hf14a_raw(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { + // Response Buffer + uint8_t resp[DEF_FIFO_LENGTH] = { 0x00 }; + uint16_t resp_length = 0; + + typedef struct { + struct { // MSB -> LSB + uint8_t reserved : 1; + + uint8_t check_response_crc : 1; + uint8_t keep_rf_field : 1; + uint8_t auto_select : 1; + uint8_t bit_frame : 1; + uint8_t append_crc : 1; + uint8_t wait_response : 1; + uint8_t open_rf_field : 1; + } options; + + // U16NTOHS + uint16_t resp_timeout; + uint16_t data_length; + + uint8_t data_buffer[0]; // We can have a lot of data or no data. struct just to compute offsets with min options. + } PACKED payload_t; + payload_t *payload = (payload_t *)data; + if (length < sizeof(payload_t)) { return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); } - nested_common_payload_t *payload = (nested_common_payload_t *)data; - status = static_nested_recover_key(bytes_to_num(payload->key_known, 6), payload->block_known, payload->type_known, payload->block_target, payload->type_target, &sncs); - if (status != HF_TAG_OK) { - return data_frame_make(cmd, status, 0, NULL); - } - // mf1_static_nested_core_t is PACKED and comprises only bytes so we can use it directly - return data_frame_make(cmd, HF_TAG_OK, sizeof(sncs), (uint8_t *)(&sncs)); + NRF_LOG_INFO("open_rf_field = %d", payload->options.open_rf_field); + NRF_LOG_INFO("wait_response = %d", payload->options.wait_response); + NRF_LOG_INFO("append_crc = %d", payload->options.append_crc); + NRF_LOG_INFO("bit_frame = %d", payload->options.bit_frame); + NRF_LOG_INFO("auto_select = %d", payload->options.auto_select); + NRF_LOG_INFO("keep_rf_field = %d", payload->options.keep_rf_field); + NRF_LOG_INFO("check_response_crc = %d", payload->options.check_response_crc); + NRF_LOG_INFO("reserved = %d", payload->options.reserved); + + status = pcd_14a_reader_raw_cmd( + payload->options.open_rf_field, + payload->options.wait_response, + payload->options.append_crc, + payload->options.bit_frame, + payload->options.auto_select, + payload->options.keep_rf_field, + payload->options.check_response_crc, + + U16NTOHS(payload->resp_timeout), + + U16NTOHS(payload->data_length), + payload->data_buffer, + + resp, + &resp_length, + U8ARR_BIT_LEN(resp) + ); + + return data_frame_make(cmd, status, resp_length, resp); } static data_frame_tx_t *cmd_processor_em410x_scan(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { @@ -937,6 +997,7 @@ static cmd_data_map_t m_data_cmd_map[] = { { DATA_CMD_MF1_AUTH_ONE_KEY_BLOCK, before_hf_reader_run, cmd_processor_mf1_auth_one_key_block, after_hf_reader_run }, { DATA_CMD_MF1_READ_ONE_BLOCK, before_hf_reader_run, cmd_processor_mf1_read_one_block, after_hf_reader_run }, { DATA_CMD_MF1_WRITE_ONE_BLOCK, before_hf_reader_run, cmd_processor_mf1_write_one_block, after_hf_reader_run }, + { DATA_CMD_HF14A_RAW, before_reader_run, cmd_processor_hf14a_raw, NULL }, { DATA_CMD_EM410X_SCAN, before_reader_run, cmd_processor_em410x_scan, NULL }, { DATA_CMD_EM410X_WRITE_TO_T55XX, before_reader_run, cmd_processor_em410x_write_to_t55XX, NULL }, diff --git a/firmware/application/src/data_cmd.h b/firmware/application/src/data_cmd.h index ce8efc8a..267e09c4 100644 --- a/firmware/application/src/data_cmd.h +++ b/firmware/application/src/data_cmd.h @@ -64,6 +64,8 @@ #define DATA_CMD_MF1_AUTH_ONE_KEY_BLOCK (2007) #define DATA_CMD_MF1_READ_ONE_BLOCK (2008) #define DATA_CMD_MF1_WRITE_ONE_BLOCK (2009) +#define DATA_CMD_HF14A_RAW (2010) + // // ****************************************************************** diff --git a/software/script/chameleon_cli_unit.py b/software/script/chameleon_cli_unit.py index 9d08c39e..df345c61 100644 --- a/software/script/chameleon_cli_unit.py +++ b/software/script/chameleon_cli_unit.py @@ -1573,3 +1573,62 @@ def on_exec(self, args: argparse.Namespace): else: print(f" Status: {response.status:#02x}") print(f" Data (HEX): {response.data.hex()}") + + +@hf_14a.command('raw', 'Send raw command') +class HF14ARaw(ReaderRequiredUnit): + + def bool_to_bit(self, value): + return 1 if value else 0 + + def args_parser(self) -> ArgumentParserNoExit or None: + parser = ArgumentParserNoExit() + parser.add_argument('-r', '--response', help="do not read response", action='store_true', default=False,) + parser.add_argument('-c', '--crc', help="calculate and append CRC", action='store_true', default=False,) + parser.add_argument('-cc', '--crc-clear', help="Verify and clear CRC of received data", action='store_true', default=False,) + parser.add_argument('-k', '--keep-rf', help="keep signal field ON after receive", action='store_true', default=False,) + parser.add_argument('-o', '--open-rf', help="active signal field ON", action='store_true', default=False,) + parser.add_argument('-s', '--select-tag', help="Select the tag before executing the command", action='store_true', default=False,) + parser.add_argument('-b', '--bits', type=int, help="number of bits to send. Useful for send partial byte") + parser.add_argument('-t', '--timeout', type=int, help="timeout in ms", default=100) + parser.add_argument('-d', '--data', type=str, help="Data to be sent") + return parser + + def on_exec(self, args: argparse.Namespace): + options = { + 'open_rf_field': self.bool_to_bit(args.open_rf), + 'wait_response': self.bool_to_bit(args.response == False), + 'append_crc': self.bool_to_bit(args.crc), + 'bit_frame': self.bool_to_bit(args.bits is not None), + 'auto_select': self.bool_to_bit(args.select_tag), + 'keep_rf_field': self.bool_to_bit(args.keep_rf), + 'check_response_crc': self.bool_to_bit(args.crc_clear), + } + data: str = args.data + if data is not None: + data = data.replace(' ', '') + if re.match(r'^[0-9a-fA-F]+$', data): + if len(data) % 2 != 0: + print(f" [!] {CR}The length of the data must be an integer multiple of 2.{C0}") + return + else: + data_bytes = bytes.fromhex(data) + else: + print(f" [!] {CR}The data must be a HEX string{C0}") + return + else: + data_bytes = [] + # Exec 14a raw cmd. + resp = self.cmd.hf14a_raw(options, args.timeout, data_bytes, args.bits) + if resp.status == chameleon_status.Device.HF_TAG_OK: + if len(resp.data) > 0: + print( + # print head + " - " + + # print data + ' '.join([ hex(byte).replace('0x', '').rjust(2, '0') for byte in resp.data ]) + ) + else: + print(F" [*] {CY}No data response{C0}") + else: + print(f" [!] {CR}{chameleon_status.message[resp.status]}{C0} ") diff --git a/software/script/chameleon_cmd.py b/software/script/chameleon_cmd.py index f12695c0..c3e12ad2 100644 --- a/software/script/chameleon_cmd.py +++ b/software/script/chameleon_cmd.py @@ -1,5 +1,6 @@ import enum import struct +import ctypes import chameleon_com import chameleon_status @@ -68,6 +69,7 @@ DATA_CMD_MF1_AUTH_ONE_KEY_BLOCK = 2007 DATA_CMD_MF1_READ_ONE_BLOCK = 2008 DATA_CMD_MF1_WRITE_ONE_BLOCK = 2009 +DATA_CMD_HF14A_RAW = 2010 DATA_CMD_EM410X_SCAN = 3000 DATA_CMD_EM410X_WRITE_TO_T55XX = 3001 @@ -550,6 +552,52 @@ def mf1_write_one_block(self, block, type_value, key, block_data): resp.data = resp.status == chameleon_status.Device.HF_TAG_OK return resp + def hf14a_raw(self, options, resp_timeout_ms=100, data=[], bit_owned_by_the_last_byte=None): + """ + Send raw cmd to 14a tag + :param options: + :param resp_timeout_ms: + :param data: + :param bit_owned_by_the_last_byte: + :return: + """ + + class CStruct(ctypes.BigEndianStructure): + _fields_ = [ + ("open_rf_field", ctypes.c_uint8, 1), + ("wait_response", ctypes.c_uint8, 1), + ("append_crc", ctypes.c_uint8, 1), + ("bit_frame", ctypes.c_uint8, 1), + ("auto_select", ctypes.c_uint8, 1), + ("keep_rf_field", ctypes.c_uint8, 1), + ("check_response_crc", ctypes.c_uint8, 1), + ("reserved", ctypes.c_uint8, 1), + ] + + cs = CStruct() + cs.open_rf_field = options['open_rf_field'] + cs.wait_response = options['wait_response'] + cs.append_crc = options['append_crc'] + cs.bit_frame = options['bit_frame'] + cs.auto_select = options['auto_select'] + cs.keep_rf_field = options['keep_rf_field'] + cs.check_response_crc = options['check_response_crc'] + + if options['bit_frame'] == 1: + bits_or_bytes = len(data) * 8 # bits = bytes * 8(bit) + if bit_owned_by_the_last_byte is not None and bit_owned_by_the_last_byte != 8: + bits_or_bytes = bits_or_bytes - (8 - bit_owned_by_the_last_byte) + else: + bits_or_bytes = len(data) # bytes length + + if len(data) > 0: + data = struct.pack(f'!BHH{len(data)}s', bytes(cs)[0], resp_timeout_ms, bits_or_bytes, bytearray(data)) + else: + data = struct.pack(f'!BHH', bytes(cs)[0], resp_timeout_ms, 0) + + return self.device.send_cmd_sync(DATA_CMD_HF14A_RAW, data, timeout=(resp_timeout_ms / 1000) + 1) + + @expect_response(chameleon_status.Device.HF_TAG_OK) def mf1_static_nested_acquire(self, block_known, type_known, key_known, block_target, type_target): """ @@ -1098,7 +1146,7 @@ def set_ble_pairing_enable(self, enabled: bool): return self.device.send_cmd_sync(DATA_CMD_SET_BLE_PAIRING_ENABLE, data) -if __name__ == '__main__': +def test_fn(): # connect to chameleon dev = chameleon_com.ChameleonCom() dev.open("com19") @@ -1108,9 +1156,58 @@ def set_ble_pairing_enable(self, enabled: bool): chip = cml.get_device_chip_id() print(f"Device chip id: {chip}") + # change to reader mode + cml.set_device_reader_mode() + + options = { + 'open_rf_field': 1, + 'wait_response': 1, + 'append_crc': 0, + 'bit_frame': 1, + 'auto_select': 0, + 'keep_rf_field': 1, + 'check_response_crc': 0, + } + + # unlock 1 + resp = cml.hf14a_raw(options=options, resp_timeout_ms=1000, data=[0x40], bit_owned_by_the_last_byte=7) + + if resp.status == 0x00 and resp.data[0] == 0x0a: + print("Gen1A unlock 1 success") + # unlock 2 + options['bit_frame'] = 0 + resp = cml.hf14a_raw(options=options, resp_timeout_ms=1000, data=[0x43]) + if resp.status == 0x00 and resp.data[0] == 0x0a: + print("Gen1A unlock 2 success") + print("Start dump gen1a memeory...") + block = 0 + while block < 64: + # Tag read block cmd + cmd_read_gen1a_block = [0x30, block] + + # Transfer with crc + options['append_crc'] = 1 + options['check_response_crc'] = 1 + resp = cml.hf14a_raw(options=options, resp_timeout_ms=100, data=cmd_read_gen1a_block) + + print(f"Block {block} : {resp.data.hex()}") + block += 1 + + # Close rf field + options['keep_rf_field'] = 0 + resp = cml.hf14a_raw(options=options) + else: + print("Gen1A unlock 2 fail") + else: + print("Gen1A unlock 1 fail") + # disconnect dev.close() # never exit while True: pass + + +if __name__ == '__main__': + test_fn() From 7da58bbce8ad16a4e4b5554eea87219160d10003 Mon Sep 17 00:00:00 2001 From: Philippe Teuwen Date: Thu, 21 Sep 2023 00:54:09 +0200 Subject: [PATCH 22/38] new tag_specific_type_t enum, new slotConfig struct. FW will take care of existing slots. Disruptive changes: see below Disruptive changes: - tag types are 2-byte long with new values - GET_SLOT_INFO returns 32 bytes as tag types are now 2-byte long - "enable" is not common to one slot anymore but bound to the HF or LF part - GET_ENABLED_SLOTS returns 16 bytes as we get "enabled_hf" and "enabled_lf" for each slot - SET_SLOT_ENABLE needs 3 bytes slot_number|sense_type|enable as we need to specify to enable HF or LF - slotConfig changed a lot but this is internal to the fw and its flash and a function does the format conversion on first boot after flash so slot data is preserved on fw upgrade, but if one downgrades to a previous version, data will be erased. --- CHANGELOG.md | 4 + docs/protocol.md | 7 +- firmware/application/src/app_cmd.c | 66 ++-- firmware/application/src/app_main.c | 14 +- .../application/src/rfid/nfctag/hf/nfc_mf1.c | 2 +- .../application/src/rfid/nfctag/hf/nfc_ntag.c | 2 +- .../src/rfid/nfctag/lf/lf_tag_em.c | 4 +- .../src/rfid/nfctag/tag_base_type.h | 95 +++++- .../src/rfid/nfctag/tag_emulation.c | 286 +++++++++++++----- .../src/rfid/nfctag/tag_emulation.h | 55 ++-- firmware/application/src/rfid_main.c | 8 +- firmware/application/src/settings.c | 3 +- firmware/application/src/utils/fds_util.c | 10 +- firmware/application/src/utils/fds_util.h | 2 +- firmware/application/src/utils/netdata.h | 16 +- firmware/common/utils.h | 15 + software/script/chameleon_cli_unit.py | 58 ++-- software/script/chameleon_cmd.py | 118 ++++++-- 18 files changed, 533 insertions(+), 232 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 422c2096..cd2845ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. This project uses the changelog in accordance with [keepchangelog](http://keepachangelog.com/). Please use this to write notable changes, which is not the same as git commit log... ## [unreleased][unreleased] + - slot_number|sense_type|enable +Disruptive changes: + - Changed slot enabled logic: now we have separate enabled_hf and enabled_lf, changed GET_ENABLED_SLOTS and SET_SLOT_ENABLE (@doegox) + - Changed tag type enum to be ready for new types, changed stored slotConfig and GET_SLOT_INFO (@doegox) - Removed MF1_DETECT_DARKSIDE (@doegox) - Added MF1_STATIC_NESTED_ACQUIRE and its support in `hf mf nested` (@xianglin1998) - Changed `hf 14a scan`: Automatically send RATS to 14443-4a tags (@augustozanellato) diff --git a/docs/protocol.md b/docs/protocol.md index d508215a..018d477b 100644 --- a/docs/protocol.md +++ b/docs/protocol.md @@ -34,9 +34,7 @@ Each command and response have their own payload formats. Standard response status is `STATUS_DEVICE_SUCCESS` for general commands, `HF_TAG_OK` for HF commands and `LF_TAG_OK` for LF commands. See [Guidelines](#new-data-payloads-guidelines-for-developers) for more info. -* **TODO:** remap `tag_specific_type_t` enum. Maybe dissociate LF & HF types in 2 enums * **TODO:** num_to_bytes bytes_to_num -* **TODO:** mf1_darkside_acquire /nested acquire deep PACKED struct... * **FIXME:** mf1_get_emulator_config with bits -> bytes (5) with 4 bools <> mf1_get_detection_log with bitfield (2)... Beware, slots in protocol count from 0 to 7 (and from 1 to 8 in the CLI...). @@ -68,7 +66,7 @@ In the following list, "CLI" refers to one typical CLI command using the describ * Response: no data * CLI: cf `hw slot init` ### 1006: SET_SLOT_ENABLE -* Command: 2 bytes. `slot_number|enable` with `slot_number` between 0 and 7 and `enable` = `0x01` to enable, `0x00` to disable +* Command: 3 bytes. `slot_number|sense_type|enable` with `slot_number` between 0 and 7, `sense_type` according to `tag_sense_type_t` enum and `enable` = `0x01` to enable, `0x00` to disable * Response: no data * CLI: cf `hw slot enable` ### 1007: SET_SLOT_TAG_NICK @@ -129,7 +127,7 @@ In the following list, "CLI" refers to one typical CLI command using the describ * CLI: cf `hw factory_reset` ### 1023: GET_ENABLED_SLOTS * Command: no data -* Response: 8 bytes, 8 bool = `0x00` or `0x01`, for slots from 0 to 7 +* Response: 16 bytes, 8*2 bool = `0x00` or `0x01`, 2 bytes for each slot from 0 to 7, as `enabled_hf|enabled_lf` ### 1024: DELETE_SLOT_SENSE_TYPE * Command: 2 bytes. `slot_number|sense_type` with `slot_number` between 0 and 7 and `sense_type` according to `tag_sense_type_t` enum. * Response: no data @@ -227,7 +225,6 @@ Notes: * `nr[4]` U32 * `ar[4]` U32 * CLI: cf `hf mf darkside` -* **FIXME:** always `CANT_FIX_NT` or watchdog reset on static nonce cards ### 2005: MF1_DETECT_NT_DIST * Command: 8 bytes: `type_known|block_known|key_known[6]`. Key as 6 bytes. * Response: 8 bytes: `uid[4]|dist[4]` diff --git a/firmware/application/src/app_cmd.c b/firmware/application/src/app_cmd.c index 3df5071b..b3879e65 100644 --- a/firmware/application/src/app_cmd.c +++ b/firmware/application/src/app_cmd.c @@ -412,10 +412,10 @@ static data_frame_tx_t *cmd_processor_hf14a_raw(uint16_t cmd, uint16_t status, u uint8_t auto_select : 1; uint8_t bit_frame : 1; uint8_t append_crc : 1; - uint8_t wait_response : 1; + uint8_t wait_response : 1; uint8_t open_rf_field : 1; } options; - + // U16NTOHS uint16_t resp_timeout; uint16_t data_length; @@ -435,16 +435,16 @@ static data_frame_tx_t *cmd_processor_hf14a_raw(uint16_t cmd, uint16_t status, u NRF_LOG_INFO("keep_rf_field = %d", payload->options.keep_rf_field); NRF_LOG_INFO("check_response_crc = %d", payload->options.check_response_crc); NRF_LOG_INFO("reserved = %d", payload->options.reserved); - + status = pcd_14a_reader_raw_cmd( payload->options.open_rf_field, - payload->options.wait_response, - payload->options.append_crc, - payload->options.bit_frame, - payload->options.auto_select, - payload->options.keep_rf_field, - payload->options.check_response_crc, - + payload->options.wait_response, + payload->options.append_crc, + payload->options.bit_frame, + payload->options.auto_select, + payload->options.keep_rf_field, + payload->options.check_response_crc, + U16NTOHS(payload->resp_timeout), U16NTOHS(payload->data_length), @@ -504,7 +504,7 @@ static data_frame_tx_t *cmd_processor_set_slot_tag_type(uint16_t cmd, uint16_t s payload_t *payload = (payload_t *)data; tag_specific_type_t tag_type = U16NTOHS(payload->tag_type); - if (payload->num_slot >= TAG_MAX_SLOT_NUM || tag_type == TAG_TYPE_UNKNOWN) { + if (payload->num_slot >= TAG_MAX_SLOT_NUM || !is_tag_specific_type_valid(tag_type)) { return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); } tag_emulation_change_type(payload->num_slot, tag_type); @@ -538,7 +538,7 @@ static data_frame_tx_t *cmd_processor_set_slot_data_default(uint16_t cmd, uint16 payload_t *payload = (payload_t *)data; tag_specific_type_t tag_type = U16NTOHS(payload->tag_type); - if (payload->num_slot >= TAG_MAX_SLOT_NUM || tag_type == TAG_TYPE_UNKNOWN) { + if (payload->num_slot >= TAG_MAX_SLOT_NUM || !is_tag_specific_type_valid(tag_type)) { return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); } status = tag_emulation_factory_data(payload->num_slot, tag_type) ? STATUS_DEVICE_SUCCESS : STATUS_NOT_IMPLEMENTED; @@ -548,20 +548,23 @@ static data_frame_tx_t *cmd_processor_set_slot_data_default(uint16_t cmd, uint16 static data_frame_tx_t *cmd_processor_set_slot_enable(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { typedef struct { uint8_t slot_index; + uint8_t sense_type; uint8_t enabled; } PACKED payload_t; payload_t *payload = (payload_t *)data; if (length != sizeof(payload_t) || payload->slot_index >= TAG_MAX_SLOT_NUM || + (payload->sense_type != TAG_SENSE_HF && payload->sense_type != TAG_SENSE_LF) || payload->enabled > 1) { return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); } uint8_t slot_now = payload->slot_index; - bool enable = payload->enabled; - tag_emulation_slot_set_enable(slot_now, enable); - if (!enable) { + tag_emulation_slot_set_enable(slot_now, payload->sense_type, payload->enabled); + if ((!payload->enabled) && + (!tag_emulation_slot_is_enabled(slot_now, payload->sense_type == TAG_SENSE_HF ? TAG_SENSE_LF : TAG_SENSE_HF))) { + // HF and LF disabled, need to change slot uint8_t slot_prev = tag_emulation_slot_find_next(slot_now); NRF_LOG_INFO("slot_now = %d, slot_prev = %d", slot_now, slot_prev); if (slot_prev == slot_now) { @@ -589,11 +592,11 @@ static data_frame_tx_t *cmd_processor_get_slot_info(uint16_t cmd, uint16_t statu uint16_t lf_tag_type; } PACKED payload[8]; - tag_specific_type_t tag_type[2]; + tag_slot_specific_type_t tag_types; for (uint8_t slot = 0; slot < 8; slot++) { - tag_emulation_get_specific_type_by_slot(slot, tag_type); - payload[slot].hf_tag_type = U16HTONS(tag_type[0]); - payload[slot].lf_tag_type = U16HTONS(tag_type[1]); + tag_emulation_get_specific_types_by_slot(slot, &tag_types); + payload[slot].hf_tag_type = U16HTONS(tag_types.tag_hf); + payload[slot].lf_tag_type = U16HTONS(tag_types.tag_lf); } return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, sizeof(payload), (uint8_t *)&payload); @@ -617,9 +620,9 @@ static data_frame_tx_t *cmd_processor_em410x_set_emu_id(uint16_t cmd, uint16_t s } static data_frame_tx_t *cmd_processor_em410x_get_emu_id(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - tag_specific_type_t tag_type[2]; - tag_emulation_get_specific_type_by_slot(tag_emulation_get_slot(), tag_type); - if (tag_type[1] == TAG_TYPE_UNKNOWN) { + tag_slot_specific_type_t tag_types; + tag_emulation_get_specific_types_by_slot(tag_emulation_get_slot(), &tag_types); + if (tag_types.tag_lf != TAG_TYPE_EM410X) { return data_frame_make(cmd, STATUS_PAR_ERR, 0, data); // no data in slot, don't send garbage } tag_data_buffer_t *buffer = get_buffer_by_tag_type(TAG_TYPE_EM410X); @@ -629,9 +632,9 @@ static data_frame_tx_t *cmd_processor_em410x_get_emu_id(uint16_t cmd, uint16_t s } static data_frame_tx_t *cmd_processor_hf14a_get_anti_coll_data(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - tag_specific_type_t tag_type[2]; - tag_emulation_get_specific_type_by_slot(tag_emulation_get_slot(), tag_type); - if (tag_type[0] == TAG_TYPE_UNKNOWN) { + tag_slot_specific_type_t tag_types; + tag_emulation_get_specific_types_by_slot(tag_emulation_get_slot(), &tag_types); + if (tag_types.tag_hf == TAG_TYPE_UNDEFINED) { return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, 0, NULL); // no data in slot, don't send garbage } nfc_tag_14a_coll_res_reference_t *info = get_saved_mifare_coll_res(); @@ -792,7 +795,8 @@ static data_frame_tx_t *cmd_processor_get_slot_tag_nick(uint16_t cmd, uint16_t s return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); } get_fds_map_by_slot_sense_type_for_nick(slot, sense_type, &map_info); - bool ret = fds_read_sync(map_info.id, map_info.key, sizeof(buffer), buffer); + uint16_t buffer_length = sizeof(buffer); + bool ret = fds_read_sync(map_info.id, map_info.key, &buffer_length, buffer); if (!ret) { return data_frame_make(cmd, STATUS_FLASH_READ_FAIL, 0, NULL); } @@ -862,11 +866,15 @@ static data_frame_tx_t *cmd_processor_mf1_set_write_mode(uint16_t cmd, uint16_t } static data_frame_tx_t *cmd_processor_get_enabled_slots(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - uint8_t slot_info[8] = {}; + struct { + uint8_t enabled_hf; + uint8_t enabled_lf; + } PACKED payload[8]; for (uint8_t slot = 0; slot < 8; slot++) { - slot_info[slot] = tag_emulation_slot_is_enable(slot); + payload[slot].enabled_hf = tag_emulation_slot_is_enabled(slot, TAG_SENSE_HF); + payload[slot].enabled_lf = tag_emulation_slot_is_enabled(slot, TAG_SENSE_LF); } - return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, sizeof(slot_info), slot_info); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, sizeof(payload), (uint8_t*)&payload); } static data_frame_tx_t *cmd_processor_get_ble_connect_key(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { diff --git a/firmware/application/src/app_main.c b/firmware/application/src/app_main.c index fbc33ad3..7380759c 100644 --- a/firmware/application/src/app_main.c +++ b/firmware/application/src/app_main.c @@ -570,8 +570,8 @@ static void btn_fn_copy_ic_uid(void) { uint8_t id_buffer[5] = { 0x00 }; // get 14a tag res buffer; uint8_t slot_now = tag_emulation_get_slot(); - tag_specific_type_t tag_type[2]; - tag_emulation_get_specific_type_by_slot(slot_now, tag_type); + tag_slot_specific_type_t tag_types; + tag_emulation_get_specific_types_by_slot(slot_now, &tag_types); nfc_tag_14a_coll_res_entity_t *antres; @@ -584,7 +584,7 @@ static void btn_fn_copy_ic_uid(void) { NRF_LOG_INFO("Start reader mode to offline copy.") } - switch (tag_type[1]) { + switch (tag_types.tag_lf) { case TAG_TYPE_EM410X: status = PcdScanEM410X(id_buffer); @@ -599,7 +599,7 @@ static void btn_fn_copy_ic_uid(void) { offline_status_error(); } break; - case TAG_TYPE_UNKNOWN: + case TAG_TYPE_UNDEFINED: // empty LF slot, nothing to do, move on to HF break; default: @@ -607,8 +607,8 @@ static void btn_fn_copy_ic_uid(void) { offline_status_error(); } - tag_data_buffer_t *buffer = get_buffer_by_tag_type(tag_type[0]); - switch (tag_type[0]) { + tag_data_buffer_t *buffer = get_buffer_by_tag_type(tag_types.tag_hf); + switch (tag_types.tag_hf) { case TAG_TYPE_MIFARE_Mini: case TAG_TYPE_MIFARE_1024: case TAG_TYPE_MIFARE_2048: @@ -626,7 +626,7 @@ static void btn_fn_copy_ic_uid(void) { break; } - case TAG_TYPE_UNKNOWN: + case TAG_TYPE_UNDEFINED: // empty HF slot, nothing to do goto exit; diff --git a/firmware/application/src/rfid/nfctag/hf/nfc_mf1.c b/firmware/application/src/rfid/nfctag/hf/nfc_mf1.c index 508f33f4..14c4ce5a 100644 --- a/firmware/application/src/rfid/nfctag/hf/nfc_mf1.c +++ b/firmware/application/src/rfid/nfctag/hf/nfc_mf1.c @@ -1114,7 +1114,7 @@ static int get_information_size_by_tag_type(tag_specific_type_t type, bool auth_ * @return The length of the data that needs to be saved is that it does not save when 0 */ int nfc_tag_mf1_data_savecb(tag_specific_type_t type, tag_data_buffer_t *buffer) { - if (m_tag_type != TAG_TYPE_UNKNOWN) { + if (m_tag_type != TAG_TYPE_UNDEFINED) { if (m_tag_information->config.mode_block_write == NFC_TAG_MF1_WRITE_SHADOW) { NRF_LOG_INFO("The mf1 is shadow write mode."); return 0; diff --git a/firmware/application/src/rfid/nfctag/hf/nfc_ntag.c b/firmware/application/src/rfid/nfctag/hf/nfc_ntag.c index b95362c3..b5627d5d 100644 --- a/firmware/application/src/rfid/nfctag/hf/nfc_ntag.c +++ b/firmware/application/src/rfid/nfctag/hf/nfc_ntag.c @@ -214,7 +214,7 @@ static int get_information_size_by_tag_type(tag_specific_type_t type) { * @return to be saved, the length of the data that needs to be saved, it means not saved when 0 */ int nfc_tag_ntag_data_savecb(tag_specific_type_t type, tag_data_buffer_t *buffer) { - if (m_tag_type != TAG_TYPE_UNKNOWN) { + if (m_tag_type != TAG_TYPE_UNDEFINED) { // Save the corresponding size data according to the current label type return get_information_size_by_tag_type(type); } else { diff --git a/firmware/application/src/rfid/nfctag/lf/lf_tag_em.c b/firmware/application/src/rfid/nfctag/lf/lf_tag_em.c index c32341ff..ef90e7dd 100644 --- a/firmware/application/src/rfid/nfctag/lf/lf_tag_em.c +++ b/firmware/application/src/rfid/nfctag/lf/lf_tag_em.c @@ -41,7 +41,7 @@ static volatile bool m_is_lf_emulating = false; // The timer of the delivery card number, we use the timer 3 const nrfx_timer_t m_timer_send_id = NRFX_TIMER_INSTANCE(3); // Cache label type -static tag_specific_type_t m_tag_type = TAG_TYPE_UNKNOWN; +static tag_specific_type_t m_tag_type = TAG_TYPE_UNDEFINED; /** * @brief Convert the card number of EM410X to the memory layout of U64 and calculate the puppet school inspection @@ -396,7 +396,7 @@ int lf_tag_em410x_data_loadcb(tag_specific_type_t type, tag_data_buffer_t *buffe */ int lf_tag_em410x_data_savecb(tag_specific_type_t type, tag_data_buffer_t *buffer) { // Make sure to load this label before allowing saving - if (m_tag_type != TAG_TYPE_UNKNOWN) { + if (m_tag_type != TAG_TYPE_UNDEFINED) { // Just save the original card package directly return LF_EM410X_TAG_ID_SIZE; } else { diff --git a/firmware/application/src/rfid/nfctag/tag_base_type.h b/firmware/application/src/rfid/nfctag/tag_base_type.h index 0e874a18..feb9473b 100644 --- a/firmware/application/src/rfid/nfctag/tag_base_type.h +++ b/firmware/application/src/rfid/nfctag/tag_base_type.h @@ -18,21 +18,100 @@ typedef enum { * Note that all the defined label type below is the specific type statistics of the application layer refine * No longer distinguish between high and low frequencies */ + typedef enum { - //Specific and necessary signs do not exist - TAG_TYPE_UNKNOWN, - //125kHz (ID card) series - TAG_TYPE_EM410X, - // MiFare series - TAG_TYPE_MIFARE_Mini, + TAG_TYPE_UNDEFINED = 0, + + // old HL/LF common types, slots using these ones need to be migrated first + OLD_TAG_TYPE_EM410X, + OLD_TAG_TYPE_MIFARE_Mini, + OLD_TAG_TYPE_MIFARE_1024, + OLD_TAG_TYPE_MIFARE_2048, + OLD_TAG_TYPE_MIFARE_4096, + OLD_TAG_TYPE_NTAG_213, + OLD_TAG_TYPE_NTAG_215, + OLD_TAG_TYPE_NTAG_216, + + //////////// LF //////////// + + //////// ASK Tag-Talk-First 100 + // EM410x + TAG_TYPE_EM410X = 100, + // FDX-B + // securakey + // gallagher + // PAC/Stanley + // Presco + // Visa2000 + // Viking + // Noralsy + // Jablotron + + //////// FSK Tag-Talk-First 200 + // HID Prox + // ioProx + // AWID + // Paradox + + //////// PSK Tag-Talk-First 300 + // Indala + // Keri + // NexWatch + + //////// Reader-Talk-First 400 + // T5577 + // EM4x05/4x69 + // EM4x50/4x70 + // Hitag series + + //////////// HF //////////// + + // MIFARE Classic series 1000 + TAG_TYPE_MIFARE_Mini = 1000, TAG_TYPE_MIFARE_1024, TAG_TYPE_MIFARE_2048, TAG_TYPE_MIFARE_4096, - // NTAG series - TAG_TYPE_NTAG_213, + // MFUL / NTAG series 1100 + TAG_TYPE_NTAG_213 = 1100, TAG_TYPE_NTAG_215, TAG_TYPE_NTAG_216, + // MIFARE Plus series 1200 + // DESFire series 1300 + + // ST25TA series 2000 + + // HF14A-4 series 3000 + } tag_specific_type_t; +#define TAG_SPECIFIC_TYPE_OLD2NEW_LF_VALUES \ + {OLD_TAG_TYPE_EM410X, TAG_TYPE_EM410X} + +#define TAG_SPECIFIC_TYPE_OLD2NEW_HF_VALUES \ + {OLD_TAG_TYPE_MIFARE_Mini, TAG_TYPE_MIFARE_Mini},\ + {OLD_TAG_TYPE_MIFARE_1024, TAG_TYPE_MIFARE_1024},\ + {OLD_TAG_TYPE_MIFARE_2048, TAG_TYPE_MIFARE_2048},\ + {OLD_TAG_TYPE_MIFARE_4096, TAG_TYPE_MIFARE_4096},\ + {OLD_TAG_TYPE_NTAG_213, TAG_TYPE_NTAG_213},\ + {OLD_TAG_TYPE_NTAG_215, TAG_TYPE_NTAG_215},\ + {OLD_TAG_TYPE_NTAG_216, TAG_TYPE_NTAG_216} + +#define TAG_SPECIFIC_TYPE_LF_VALUES \ + TAG_TYPE_EM410X + +#define TAG_SPECIFIC_TYPE_HF_VALUES \ + TAG_TYPE_MIFARE_Mini,\ + TAG_TYPE_MIFARE_1024,\ + TAG_TYPE_MIFARE_2048,\ + TAG_TYPE_MIFARE_4096,\ + TAG_TYPE_NTAG_213,\ + TAG_TYPE_NTAG_215,\ + TAG_TYPE_NTAG_216 + +typedef struct { + tag_specific_type_t tag_hf; + tag_specific_type_t tag_lf; +} tag_slot_specific_type_t; + #endif diff --git a/firmware/application/src/rfid/nfctag/tag_emulation.c b/firmware/application/src/rfid/nfctag/tag_emulation.c index a3d7079d..9819d67f 100644 --- a/firmware/application/src/rfid/nfctag/tag_emulation.c +++ b/firmware/application/src/rfid/nfctag/tag_emulation.c @@ -32,7 +32,21 @@ NRF_LOG_MODULE_REGISTER(); // Is the logo in the analog card? bool g_is_tag_emulating = false; +static tag_specific_type_t tag_specific_type_old2new_lf_values[][2] = { TAG_SPECIFIC_TYPE_OLD2NEW_LF_VALUES }; +static tag_specific_type_t tag_specific_type_old2new_hf_values[][2] = { TAG_SPECIFIC_TYPE_OLD2NEW_HF_VALUES }; +static tag_specific_type_t tag_specific_type_lf_values[] = { TAG_SPECIFIC_TYPE_LF_VALUES }; +static tag_specific_type_t tag_specific_type_hf_values[] = { TAG_SPECIFIC_TYPE_HF_VALUES }; +bool is_tag_specific_type_valid(tag_specific_type_t tag_type) { + bool valid = false; + for (uint16_t i=0; i < ARRAYLEN(tag_specific_type_lf_values); i++) { + valid |= (tag_type == tag_specific_type_lf_values[i]); + } + for (uint16_t i=0; i < ARRAYLEN(tag_specific_type_hf_values); i++) { + valid |= (tag_type == tag_specific_type_hf_values[i]); + } + return valid; +} // ********************** Specific parameters start ********************** /** @@ -51,17 +65,19 @@ static tag_data_buffer_t m_tag_data_hf = { sizeof(m_tag_data_buffer_hf), m_tag_d */ static tag_slot_config_t slotConfig ALIGN_U32 = { // Configure activated card slot, default activation of the 0th card slot (the first card) - .config = { .activated = 0, .reserved1 = 0, .reserved2 = 0, .reserved3 = 0, }, - // Configuration card slot group - .group = { - { .enable = true, .reserved1 = 0, .reserved2 = 0, .tag_hf = TAG_TYPE_MIFARE_1024, .tag_lf = TAG_TYPE_EM410X, }, // 1 - { .enable = true, .reserved1 = 0, .reserved2 = 0, .tag_hf = TAG_TYPE_MIFARE_1024, .tag_lf = TAG_TYPE_UNKNOWN, }, // 2 - { .enable = true, .reserved1 = 0, .reserved2 = 0, .tag_hf = TAG_TYPE_UNKNOWN, .tag_lf = TAG_TYPE_EM410X, }, // 3 - { .enable = false, .reserved1 = 0, .reserved2 = 0, .tag_hf = TAG_TYPE_UNKNOWN, .tag_lf = TAG_TYPE_UNKNOWN, }, // 4 - { .enable = false, .reserved1 = 0, .reserved2 = 0, .tag_hf = TAG_TYPE_UNKNOWN, .tag_lf = TAG_TYPE_UNKNOWN, }, // 5 - { .enable = false, .reserved1 = 0, .reserved2 = 0, .tag_hf = TAG_TYPE_UNKNOWN, .tag_lf = TAG_TYPE_UNKNOWN, }, // 6 - { .enable = false, .reserved1 = 0, .reserved2 = 0, .tag_hf = TAG_TYPE_UNKNOWN, .tag_lf = TAG_TYPE_UNKNOWN, }, // 7 - { .enable = false, .reserved1 = 0, .reserved2 = 0, .tag_hf = TAG_TYPE_UNKNOWN, .tag_lf = TAG_TYPE_UNKNOWN, }, // 8 + .version = TAG_SLOT_CONFIG_CURRENT_VERSION, + .active_slot = 0, + // Configuration card slots + // See tag_emulation_factory_init for actual tag content + .slots = { + { .enabled_hf = true, .enabled_lf = true, .tag_hf = TAG_TYPE_MIFARE_1024, .tag_lf = TAG_TYPE_EM410X, }, // 1 + { .enabled_hf = true, .enabled_lf = false, .tag_hf = TAG_TYPE_MIFARE_1024, .tag_lf = TAG_TYPE_UNDEFINED, }, // 2 + { .enabled_hf = true, .enabled_lf = true, .tag_hf = TAG_TYPE_UNDEFINED, .tag_lf = TAG_TYPE_EM410X, }, // 3 + { .enabled_hf = false, .enabled_lf = false, .tag_hf = TAG_TYPE_UNDEFINED, .tag_lf = TAG_TYPE_UNDEFINED, }, // 4 + { .enabled_hf = false, .enabled_lf = false, .tag_hf = TAG_TYPE_UNDEFINED, .tag_lf = TAG_TYPE_UNDEFINED, }, // 5 + { .enabled_hf = false, .enabled_lf = false, .tag_hf = TAG_TYPE_UNDEFINED, .tag_lf = TAG_TYPE_UNDEFINED, }, // 6 + { .enabled_hf = false, .enabled_lf = false, .tag_hf = TAG_TYPE_UNDEFINED, .tag_lf = TAG_TYPE_UNDEFINED, }, // 7 + { .enabled_hf = false, .enabled_lf = false, .tag_hf = TAG_TYPE_UNDEFINED, .tag_lf = TAG_TYPE_UNDEFINED, }, // 8 }, }; // The card slot configuration unique CRC, once the slot configuration changes, can be checked by CRC @@ -90,6 +106,9 @@ static tag_base_handler_map_t tag_base_map[] = { }; +static void tag_emulation_load_config(void); +static void tag_emulation_save_config(void); + /** * accordingToTheSpecifiedDetailedLabelType,ObtainTheImplementationFunctionOfTheDataThatProcessesTheLoadedLoaded */ @@ -176,7 +195,7 @@ bool tag_emulation_load_by_buffer(tag_specific_type_t tag_type, bool update_crc) */ static void load_data_by_tag_type(uint8_t slot, tag_specific_type_t tag_type) { // maybeTheCardSlotIsNotEnabledToUseTheSimulationOfThisTypeOfLabel,AndSkipTheDataDirectlyToLoadThisData - if (tag_type == TAG_TYPE_UNKNOWN) { + if (tag_type == TAG_TYPE_UNDEFINED) { return; } // getTheSpecialBufferInformation @@ -190,7 +209,8 @@ static void load_data_by_tag_type(uint8_t slot, tag_specific_type_t tag_type) { fds_slot_record_map_t map_info; get_fds_map_by_slot_sense_type_for_dump(slot, sense_type, &map_info); // accordingToTheTypeOfTheCardSlotCurrentlyActivated,LoadTheDataOfTheDesignatedFieldToTheBuffer //Tip:IfTheLengthOfTheDataCannotMatchTheLengthOfTheBuffer,ItMayBeCausedByTheFirmwareUpdateAtThisTime,TheDataMustBeDeletedAndRebuilt - bool ret = fds_read_sync(map_info.id, map_info.key, buffer->length, buffer->buffer); + uint16_t length = buffer->length; + bool ret = fds_read_sync(map_info.id, map_info.key, &length, buffer->buffer); if (false == ret) { NRF_LOG_INFO("Tag slot data no exists."); return; @@ -206,7 +226,7 @@ static void load_data_by_tag_type(uint8_t slot, tag_specific_type_t tag_type) { */ static void save_data_by_tag_type(uint8_t slot, tag_specific_type_t tag_type) { // Maybe the card slot is not enabled to use the simulation of this type of label, and skip it directly to save this data - if (tag_type == TAG_TYPE_UNKNOWN) { + if (tag_type == TAG_TYPE_UNDEFINED) { return; } tag_data_buffer_t *buffer = get_buffer_by_tag_type(tag_type); @@ -276,8 +296,8 @@ static void delete_data_by_tag_type(uint8_t slot, tag_sense_type_t sense_type) { */ void tag_emulation_load_data(void) { uint8_t slot = tag_emulation_get_slot(); - load_data_by_tag_type(slot, slotConfig.group[slot].tag_hf); - load_data_by_tag_type(slot, slotConfig.group[slot].tag_lf); + load_data_by_tag_type(slot, slotConfig.slots[slot].tag_hf); + load_data_by_tag_type(slot, slotConfig.slots[slot].tag_lf); } /** @@ -285,8 +305,8 @@ void tag_emulation_load_data(void) { */ void tag_emulation_save_data(void) { uint8_t slot = tag_emulation_get_slot(); - save_data_by_tag_type(slot, slotConfig.group[slot].tag_hf); - save_data_by_tag_type(slot, slotConfig.group[slot].tag_lf); + save_data_by_tag_type(slot, slotConfig.slots[slot].tag_hf); + save_data_by_tag_type(slot, slotConfig.slots[slot].tag_lf); } /** @@ -295,9 +315,9 @@ void tag_emulation_save_data(void) { * @param slot Card slot * @param tag_type Label */ -void tag_emulation_get_specific_type_by_slot(uint8_t slot, tag_specific_type_t tag_type[2]) { - tag_type[0] = slotConfig.group[slot].tag_hf; - tag_type[1] = slotConfig.group[slot].tag_lf; +void tag_emulation_get_specific_types_by_slot(uint8_t slot, tag_slot_specific_type_t *tag_types) { + tag_types->tag_hf = slotConfig.slots[slot].tag_hf; + tag_types->tag_lf = slotConfig.slots[slot].tag_lf; } /** @@ -309,24 +329,22 @@ void tag_emulation_delete_data(uint8_t slot, tag_sense_type_t sense_type) { //Close the corresponding card type of the corresponding card slot switch (sense_type) { case TAG_SENSE_HF: { - slotConfig.group[slot].tag_hf = TAG_TYPE_UNKNOWN; + slotConfig.slots[slot].tag_hf = TAG_TYPE_UNDEFINED; + slotConfig.slots[slot].enabled_hf = false; } break; case TAG_SENSE_LF: { - slotConfig.group[slot].tag_lf = TAG_TYPE_UNKNOWN; + slotConfig.slots[slot].tag_lf = TAG_TYPE_UNDEFINED; + slotConfig.slots[slot].enabled_lf = false; } break; default: break; } // If the deleted card slot data is currently activated (being simulated), we also need to make dynamic shutdown - if (slotConfig.config.activated == slot) { + if (slotConfig.active_slot == slot) { tag_emulation_sense_switch(sense_type, false); } - // If we find that the two cards of this card groove are gone, we have to close this card slot. - if (slotConfig.group[slot].tag_hf == TAG_TYPE_UNKNOWN && slotConfig.group[slot].tag_lf == TAG_TYPE_UNKNOWN) { - slotConfig.group[slot].enable = false; - } } /** @@ -353,14 +371,14 @@ bool tag_emulation_factory_data(uint8_t slot, tag_specific_type_t tag_type) { */ static void tag_emulation_sense_switch_all(bool enable) { uint8_t slot = tag_emulation_get_slot(); - // NRF_LOG_INFO("Slot %d tag type hf %d, lf %d", slot, slotConfig.group[slot].tag_hf, slotConfig.group[slot].tag_lf); - if (slotConfig.group[slot].tag_hf != TAG_TYPE_UNKNOWN) { - nfc_tag_14a_sense_switch(enable); + // NRF_LOG_INFO("Slot %d tag type hf %d, lf %d", slot, slotConfig.slots[slot].tag_hf, slotConfig.slots[slot].tag_lf); + if (enable && (slotConfig.slots[slot].enabled_hf) && (slotConfig.slots[slot].tag_hf != TAG_TYPE_UNDEFINED)) { + nfc_tag_14a_sense_switch(true); } else { nfc_tag_14a_sense_switch(false); } - if (slotConfig.group[slot].tag_lf != TAG_TYPE_UNKNOWN) { - lf_tag_125khz_sense_switch(enable); + if (enable && (slotConfig.slots[slot].enabled_lf) && (slotConfig.slots[slot].tag_lf != TAG_TYPE_UNDEFINED)) { + lf_tag_125khz_sense_switch(true); } else { lf_tag_125khz_sense_switch(false); } @@ -372,33 +390,116 @@ static void tag_emulation_sense_switch_all(bool enable) { * @param enable: Whether to enable this type of field induction */ void tag_emulation_sense_switch(tag_sense_type_t type, bool enable) { + uint8_t slot = tag_emulation_get_slot(); // Check the parameters, not allowed to switch non -normal field - if (type == TAG_SENSE_NO) APP_ERROR_CHECK(NRF_ERROR_INVALID_PARAM); - // Switch high frequency - if (type == TAG_SENSE_HF) nfc_tag_14a_sense_switch(enable); - // Switch low frequency - if (type == TAG_SENSE_LF) lf_tag_125khz_sense_switch(enable); + switch (type) { + case TAG_SENSE_NO: + APP_ERROR_CHECK(NRF_ERROR_INVALID_PARAM); + break; + case TAG_SENSE_HF: + if (enable && (slotConfig.slots[slot].enabled_hf) && + (slotConfig.slots[slot].tag_hf != TAG_TYPE_UNDEFINED)) { + nfc_tag_14a_sense_switch(true); + } else { + nfc_tag_14a_sense_switch(false); + } + break; + case TAG_SENSE_LF: + if (enable && (slotConfig.slots[slot].enabled_lf) && + (slotConfig.slots[slot].tag_lf != TAG_TYPE_UNDEFINED)) { + lf_tag_125khz_sense_switch(true); + } else { + lf_tag_125khz_sense_switch(false); + } + break; + } +} + + +static void tag_emulation_migrate_slot_config_v0_to_v8(void) { + // Copy old slotConfig content + uint8_t tmpbuf[sizeof(slotConfig)]; + memcpy(tmpbuf, (uint8_t *)&slotConfig, sizeof(tmpbuf)); + NRF_LOG_INFO("Migrating slotConfig v0..."); + NRF_LOG_HEXDUMP_INFO(tmpbuf, sizeof(tmpbuf)); + // Populate new slotConfig struct + slotConfig.version = TAG_SLOT_CONFIG_CURRENT_VERSION; + slotConfig.active_slot = tmpbuf[0]; + for (uint8_t i = 0; i< ARRAYLEN(slotConfig.slots); i++) { + bool enabled = tmpbuf[4+(i*4)] & 1; + + slotConfig.slots[i].tag_hf = tmpbuf[4+(i*4)+2]; + for (uint8_t j=0; j < ARRAYLEN(tag_specific_type_old2new_hf_values); j++) { + if (slotConfig.slots[i].tag_hf == tag_specific_type_old2new_hf_values[j][0]) { + slotConfig.slots[i].tag_hf = tag_specific_type_old2new_hf_values[j][1]; + } + } + slotConfig.slots[i].enabled_hf = slotConfig.slots[i].tag_hf != TAG_TYPE_UNDEFINED ? enabled : false; + NRF_LOG_INFO("Slot %i HF: %02X->%04X enabled:%i", i, tmpbuf[4+(i*4)+2], slotConfig.slots[i].tag_hf, slotConfig.slots[i].enabled_hf); + + slotConfig.slots[i].tag_lf = tmpbuf[4+(i*4)+3]; + for (uint8_t j=0; j < ARRAYLEN(tag_specific_type_old2new_lf_values); j++) { + if (slotConfig.slots[i].tag_lf == tag_specific_type_old2new_lf_values[j][0]) { + slotConfig.slots[i].tag_lf = tag_specific_type_old2new_lf_values[j][1]; + } + } + slotConfig.slots[i].enabled_lf = slotConfig.slots[i].tag_lf != TAG_TYPE_UNDEFINED ? enabled : false; + NRF_LOG_INFO("Slot %i LF: %02X->%04X enabled:%i", i, tmpbuf[4+(i*4)+3], slotConfig.slots[i].tag_lf, slotConfig.slots[i].enabled_lf); + } +} + + +static void tag_emulation_migrate_slot_config(void) { + switch(slotConfig.version) { + case 0: + case 1: + case 2: + case 3: + case 4: + case 5: + case 6: + case 7: + tag_emulation_migrate_slot_config_v0_to_v8(); + + /* + * Add new migration steps ABOVE THIS COMMENT + * `tag_emulation_save_config()` and `break` statements should only be used on the last migration step, all the previous steps must fall + * through to the next case. + */ + + tag_emulation_save_config(); + case TAG_SLOT_CONFIG_CURRENT_VERSION: + break; + default: + NRF_LOG_ERROR("Unsupported slotConfig migration attempted! (%d -> %d)", slotConfig.version, TAG_SLOT_CONFIG_CURRENT_VERSION); + break; + } } + /** * Load the emulated card configuration data, note that loading is just a card slot configuration */ -void tag_emulation_load_config(void) { +static void tag_emulation_load_config(void) { + uint16_t length = sizeof(slotConfig); // Read the card slot configuration data - bool ret = fds_read_sync(FDS_EMULATION_CONFIG_FILE_ID, FDS_EMULATION_CONFIG_RECORD_KEY, sizeof(slotConfig), (uint8_t *)&slotConfig); + bool ret = fds_read_sync(FDS_EMULATION_CONFIG_FILE_ID, FDS_EMULATION_CONFIG_RECORD_KEY, &length, (uint8_t *)&slotConfig); if (ret) { // After the reading is completed, we will save a BCC of the current configuration. When it is stored later, it can be used as a reference for the contrast between changes. calc_14a_crc_lut((uint8_t *)&slotConfig, sizeof(slotConfig), (uint8_t *)&m_slot_config_crc); NRF_LOG_INFO("Load tag slot config done."); + if (slotConfig.version < TAG_SLOT_CONFIG_CURRENT_VERSION) { // old slotConfig, need to migrate + tag_emulation_migrate_slot_config(); + } } else { - NRF_LOG_INFO("Tag slot config no exists."); + NRF_LOG_INFO("Tag slot config does not exist."); } } /** *Save the emulated card configuration data */ -void tag_emulation_save_config(void) { +static void tag_emulation_save_config(void) { // We are configured the card slot configuration, and we need to calculate the current card slot configuration CRC code to judge whether the data below is updated uint16_t new_calc_crc; calc_14a_crc_lut((uint8_t *)&slotConfig, sizeof(slotConfig), (uint8_t *)&new_calc_crc); @@ -452,14 +553,14 @@ void tag_emulation_save(void) { * Get the currently activated card slot index */ uint8_t tag_emulation_get_slot(void) { - return slotConfig.config.activated; + return slotConfig.active_slot; } /** * Set the currently activated card slot index */ void tag_emulation_set_slot(uint8_t index) { - slotConfig.config.activated = index; // Re -set to the new switched card slot + slotConfig.active_slot = index; // Re -set to the new switched card slot rgb_marquee_reset(); // force animation color refresh according to new slot } @@ -484,31 +585,52 @@ void tag_emulation_change_slot(uint8_t index, bool sense_disable) { /** * Determine whether the specified card slot is enabled */ -bool tag_emulation_slot_is_enable(uint8_t slot) { - //Return to the capacity of the corresponding card slot directly - return slotConfig.group[slot].enable; +bool tag_emulation_slot_is_enabled(uint8_t slot, tag_sense_type_t sense_type) { + switch (sense_type) { + case TAG_SENSE_LF: { + return slotConfig.slots[slot].enabled_lf; + break; + } + case TAG_SENSE_HF: { + return slotConfig.slots[slot].enabled_hf; + break; + } + default: + return false; + break; //Never happen + } } /** * Set whether the specified card slot is enabled */ -void tag_emulation_slot_set_enable(uint8_t slot, bool enable) { +void tag_emulation_slot_set_enable(uint8_t slot, tag_sense_type_t sense_type, bool enable) { //Set the capacity of the corresponding card slot directly - slotConfig.group[slot].enable = enable; + switch (sense_type) { + case TAG_SENSE_LF: { + slotConfig.slots[slot].enabled_lf = enable; + break; + } + case TAG_SENSE_HF: { + slotConfig.slots[slot].enabled_hf = enable; + break; + } + default: + break; //Never happen + } } /** *Find the next valid card slot */ uint8_t tag_emulation_slot_find_next(uint8_t slot_now) { - uint8_t start_slot = (slot_now + 1 >= TAG_MAX_SLOT_NUM) ? 0 : slot_now + 1; - for (uint8_t i = start_slot; i < sizeof(slotConfig.group);) { - if (i == slot_now) return slot_now; // No other activated card slots were found after a reincarnation - if (slotConfig.group[i].enable) return i; // Check whether the card slot that is currently traversed is enabled, so that the capacity determines that the current card slot is the card slot that can effectively enable capacity - if (i + 1 >= TAG_MAX_SLOT_NUM) { // Continue the next cycle + uint8_t start_slot = (slot_now + 1 == TAG_MAX_SLOT_NUM) ? 0 : slot_now + 1; + for (uint8_t i = start_slot;;) { + if (i == slot_now) return slot_now; // No other activated card slots were found after a loop + if (slotConfig.slots[i].enabled_hf || slotConfig.slots[i].enabled_lf ) return i; // Check whether the card slot that is currently traversed is enabled, so that the capacity determines that the current card slot is the card slot that can effectively enable capacity + i++; + if (i == TAG_MAX_SLOT_NUM) { // Continue the next cycle i = 0; - } else { - i += 1; } } return slot_now; // If you cannot find it, the specified return value of the pass is returned by default @@ -518,14 +640,14 @@ uint8_t tag_emulation_slot_find_next(uint8_t slot_now) { * Find the previous valid card slot */ uint8_t tag_emulation_slot_find_prev(uint8_t slot_now) { - uint8_t start_slot = (slot_now - 1 < 0) ? (TAG_MAX_SLOT_NUM - 1) : slot_now - 1; - for (uint8_t i = start_slot; i < sizeof(slotConfig.group);) { - if (i == slot_now) return slot_now; //No other activated card slots were found after a reincarnation - if (slotConfig.group[i].enable) return i; // Check whether the card slot that is currently traversed is enabled, so that the capacity determines that the current card slot is the card slot that can effectively enable capacity - if (i - 1 < 0) { // Continue the next cycle - i = (TAG_MAX_SLOT_NUM - 1); + uint8_t start_slot = (slot_now == 0) ? (TAG_MAX_SLOT_NUM - 1) : slot_now - 1; + for (uint8_t i = start_slot;;) { + if (i == slot_now) return slot_now; //No other activated card slots were found after a loop + if (slotConfig.slots[i].enabled_hf || slotConfig.slots[i].enabled_lf) return i; // Check whether the card slot that is currently traversed is enabled, so that the capacity determines that the current card slot is the card slot that can effectively enable capacity + if (i == 0) { // Continue the next cycle + i = TAG_MAX_SLOT_NUM - 1; } else { - i -= 1; + i--; } } return slot_now; // If you cannot find it, the specified return value of the pass is returned by default @@ -539,11 +661,11 @@ void tag_emulation_change_type(uint8_t slot, tag_specific_type_t tag_type) { NRF_LOG_INFO("sense type = %d", sense_type); switch (sense_type) { case TAG_SENSE_LF: { - slotConfig.group[slot].tag_lf = tag_type; + slotConfig.slots[slot].tag_lf = tag_type; break; } case TAG_SENSE_HF: { - slotConfig.group[slot].tag_hf = tag_type; + slotConfig.slots[slot].tag_hf = tag_type; break; } default: @@ -564,34 +686,36 @@ void tag_emulation_change_type(uint8_t slot, tag_specific_type_t tag_type) { void tag_emulation_factory_init(void) { fds_slot_record_map_t map_info; - if (slotConfig.group[0].enable && slotConfig.group[0].tag_hf != TAG_TYPE_UNKNOWN && slotConfig.group[0].tag_lf != TAG_TYPE_UNKNOWN) { - // Initialized a dual -frequency card in the card slot, if there is no historical record, it is a new state of factory. + // Initialized a dual -frequency card in the card slot, if there is no historical record, it is a new state of factory. + if (slotConfig.slots[0].enabled_hf && slotConfig.slots[0].tag_hf == TAG_TYPE_MIFARE_1024) { + // Initialize a high -frequency M1 card in the card slot 1, if it does not exist. get_fds_map_by_slot_sense_type_for_dump(0, TAG_SENSE_HF, &map_info); - bool is_slot1_hf_data_exists = fds_is_exists(map_info.id, map_info.key); + if (!fds_is_exists(map_info.id, map_info.key)) { + tag_emulation_factory_data(0, slotConfig.slots[0].tag_hf); + } + } + + if (slotConfig.slots[0].enabled_lf && slotConfig.slots[0].tag_lf == TAG_TYPE_EM410X) { + // Initialize a low -frequency EM410X card in slot 1, if it does not exist. get_fds_map_by_slot_sense_type_for_dump(0, TAG_SENSE_LF, &map_info); - bool is_slot1_lf_data_exists = fds_is_exists(map_info.id, map_info.key); - // Here are no high -frequency cards and low -frequency cards of card slot 1 here. - if (!is_slot1_hf_data_exists && !is_slot1_lf_data_exists) { - tag_emulation_factory_data(0, slotConfig.group[0].tag_hf); - tag_emulation_factory_data(0, slotConfig.group[0].tag_lf); + if (!fds_is_exists(map_info.id, map_info.key)) { + tag_emulation_factory_data(0, slotConfig.slots[0].tag_lf); } } - if (slotConfig.group[1].enable && slotConfig.group[1].tag_hf != TAG_TYPE_UNKNOWN) { + if (slotConfig.slots[1].enabled_hf && slotConfig.slots[1].tag_hf == TAG_TYPE_MIFARE_1024) { // Initialize a high -frequency M1 card in the card slot 2, if it does not exist. get_fds_map_by_slot_sense_type_for_dump(1, TAG_SENSE_HF, &map_info); - bool is_slot2_hf_data_exists = fds_is_exists(map_info.id, map_info.key); - if (!is_slot2_hf_data_exists) { - tag_emulation_factory_data(1, slotConfig.group[1].tag_hf); + if (!fds_is_exists(map_info.id, map_info.key)) { + tag_emulation_factory_data(1, slotConfig.slots[1].tag_hf); } } - if (slotConfig.group[2].enable && slotConfig.group[2].tag_lf != TAG_TYPE_UNKNOWN) { + if (slotConfig.slots[2].enabled_lf && slotConfig.slots[2].tag_lf == TAG_TYPE_EM410X) { // Initialize a low -frequency EM410X card in slot 3, if it does not exist. get_fds_map_by_slot_sense_type_for_dump(2, TAG_SENSE_LF, &map_info); - bool is_slot3_lf_data_exists = fds_is_exists(map_info.id, map_info.key); - if (!is_slot3_lf_data_exists) { - tag_emulation_factory_data(2, slotConfig.group[2].tag_lf); + if (!fds_is_exists(map_info.id, map_info.key)) { + tag_emulation_factory_data(2, slotConfig.slots[2].tag_lf); } } } diff --git a/firmware/application/src/rfid/nfctag/tag_emulation.h b/firmware/application/src/rfid/nfctag/tag_emulation.h index d583cd75..f17a9c5b 100644 --- a/firmware/application/src/rfid/nfctag/tag_emulation.h +++ b/firmware/application/src/rfid/nfctag/tag_emulation.h @@ -4,7 +4,7 @@ #include #include #include - +#include "app_util.h" #include "utils.h" #include "tag_base_type.h" @@ -44,26 +44,34 @@ typedef struct { * This configuration can be preserved by persistently to Flash * 4 bytes a word, keep in mind the entire word alignment */ -typedef struct ALIGN_U32 { - //Basic configuration - struct { - uint8_t activated; //Which card slot is currently activated (which card slot is used) - uint8_t reserved1; // reserve - uint8_t reserved2; //reserve - uint8_t reserved3; // reserve - } config; - // The configuration of each card slot itself - struct { - //Basic configuration, occupying two bytes - uint8_t enable: 1; // Whether to enable the card - uint8_t reserved1: 7; // reserve - uint8_t reserved2; //reserve - // Specific type of simulation card - tag_specific_type_t tag_hf; - tag_specific_type_t tag_lf; - } group[TAG_MAX_SLOT_NUM]; -} tag_slot_config_t; +#define TAG_SLOT_CONFIG_CURRENT_VERSION 8 +// Intended struct size, for static assert +#define TAG_SLOT_CONFIG_CURRENT_SIZE 68 +typedef struct { + //Basic configuration + uint8_t version; // struct version (U8 so map on old .activated<=7 field) + uint8_t active_slot; // Which slot is currently active + uint32_t : 0; // U32 align + struct { // 4-byte slot config + 2*2-byte tag_specific_types + // Individual slot configuration + uint32_t enabled_hf : 1; // Whether to enable the HF card + uint32_t enabled_lf : 1; // Whether to enable the LF card + uint32_t : 0; // U32 align + // Specific type of emulated card + union { + uint16_t U16_tag_hf; + tag_specific_type_t tag_hf; + }; + union { + uint16_t U16_tag_lf; + tag_specific_type_t tag_lf; + }; + } slots[TAG_MAX_SLOT_NUM]; +} PACKED tag_slot_config_t; + +// Use the macro to check the struct size +STATIC_ASSERT(sizeof(tag_slot_config_t) == TAG_SLOT_CONFIG_CURRENT_SIZE); // The most basic simulation card initialization program void tag_emulation_init(void); @@ -95,16 +103,17 @@ uint8_t tag_emulation_get_slot(void); // Switch the card slot to control whether the passing parameter control is closed during the switching period to listen to void tag_emulation_change_slot(uint8_t index, bool sense_disable); // Get the card slot to enable the state -bool tag_emulation_slot_is_enable(uint8_t slot); +bool tag_emulation_slot_is_enabled(uint8_t slot, tag_sense_type_t sense_type); // Set the card slot to enable -void tag_emulation_slot_set_enable(uint8_t slot, bool enable); +void tag_emulation_slot_set_enable(uint8_t slot, tag_sense_type_t sense_type, bool enable); // Get the simulation card type of the corresponding card slot -void tag_emulation_get_specific_type_by_slot(uint8_t slot, tag_specific_type_t tag_type[2]); +void tag_emulation_get_specific_types_by_slot(uint8_t slot, tag_slot_specific_type_t *tag_types); // Initialize some factory data void tag_emulation_factory_init(void); //In the direction, query any card slot that enable uint8_t tag_emulation_slot_find_next(uint8_t slot_now); uint8_t tag_emulation_slot_find_prev(uint8_t slot_now); +bool is_tag_specific_type_valid(tag_specific_type_t tag_type); #endif diff --git a/firmware/application/src/rfid_main.c b/firmware/application/src/rfid_main.c index 83fb273f..d6861bf5 100644 --- a/firmware/application/src/rfid_main.c +++ b/firmware/application/src/rfid_main.c @@ -94,11 +94,11 @@ device_mode_t get_device_mode(void) { * @return uint8_t Color 0R, 1G, 2B */ uint8_t get_color_by_slot(uint8_t slot) { - tag_specific_type_t tag_type[2]; - tag_emulation_get_specific_type_by_slot(slot, tag_type); - if (tag_type[0] != TAG_TYPE_UNKNOWN && tag_type[1] != TAG_TYPE_UNKNOWN) { + tag_slot_specific_type_t tag_types; + tag_emulation_get_specific_types_by_slot(slot, &tag_types); + if (tag_types.tag_hf != TAG_TYPE_UNDEFINED && tag_types.tag_lf != TAG_TYPE_UNDEFINED) { return 0; // Dual -frequency card simulation, return R, indicate a dual -frequency card - } else if (tag_type[0] != TAG_TYPE_UNKNOWN) { //High -frequency simulation, return G + } else if (tag_types.tag_hf != TAG_TYPE_UNDEFINED) { //High -frequency simulation, return G return 1; } else { // Low -frequency simulation, return B return 2; diff --git a/firmware/application/src/settings.c b/firmware/application/src/settings.c index 638e9b17..159d54a8 100644 --- a/firmware/application/src/settings.c +++ b/firmware/application/src/settings.c @@ -95,7 +95,8 @@ void settings_migrate(void) { } void settings_load_config(void) { - bool ret = fds_read_sync(FDS_SETTINGS_FILE_ID, FDS_SETTINGS_RECORD_KEY, sizeof(config), (uint8_t *)&config); + uint16_t length = sizeof(config); + bool ret = fds_read_sync(FDS_SETTINGS_FILE_ID, FDS_SETTINGS_RECORD_KEY, &length, (uint8_t *)&config); if (ret) { NRF_LOG_INFO("Load config done."); // After the reading is complete, we first save a copy of the current CRC, which can be used as a reference for comparison of changes when saving later diff --git a/firmware/application/src/utils/fds_util.c b/firmware/application/src/utils/fds_util.c index 9edc639d..8de52c7e 100644 --- a/firmware/application/src/utils/fds_util.c +++ b/firmware/application/src/utils/fds_util.c @@ -49,26 +49,30 @@ bool fds_is_exists(uint16_t id, uint16_t key) { /** *Read record + * Length: set it to max length (size of buffer) + * After execution, length is updated to the real flash record size */ -bool fds_read_sync(uint16_t id, uint16_t key, uint16_t max_length, uint8_t *buffer) { +bool fds_read_sync(uint16_t id, uint16_t key, uint16_t *length, uint8_t *buffer) { ret_code_t err_code; //The results of the operation fds_flash_record_t flash_record; // Pointing to the actual information in Flash fds_record_desc_t record_desc; // Recorded handle if (fds_find_record(id, key, &record_desc)) { err_code = fds_record_open(&record_desc, &flash_record); //Open the record so that it is marked as the open state APP_ERROR_CHECK(err_code); - if (flash_record.p_header->length_words * 4 <= max_length) { // Read the data in Flash here to the given RAM + if (flash_record.p_header->length_words * 4 <= *length) { // Read the data in Flash here to the given RAM // Make sure that the buffer will not overflow, read this record memcpy(buffer, flash_record.p_data, flash_record.p_header->length_words * 4); NRF_LOG_INFO("FDS read success."); + *length = flash_record.p_header->length_words * 4; return true; } else { - NRF_LOG_INFO("FDS buffer too small, can't run memcpy, fds size = %d, buffer size = %d", flash_record.p_header->length_words * 4, max_length); + NRF_LOG_INFO("FDS buffer too small, can't run memcpy, fds size = %d, buffer size = %d", flash_record.p_header->length_words * 4, *length); } err_code = fds_record_close(&record_desc); // Close the file after the operation is completed APP_ERROR_CHECK(err_code); } //If the correct data is not loaded, this record may not exist + *length = 0; return false; } diff --git a/firmware/application/src/utils/fds_util.h b/firmware/application/src/utils/fds_util.h index feb477a0..76909487 100644 --- a/firmware/application/src/utils/fds_util.h +++ b/firmware/application/src/utils/fds_util.h @@ -4,7 +4,7 @@ #include "fds.h" -bool fds_read_sync(uint16_t id, uint16_t key, uint16_t max_length, uint8_t *buffer); +bool fds_read_sync(uint16_t id, uint16_t key, uint16_t *length, uint8_t *buffer); bool fds_write_sync(uint16_t id, uint16_t key, uint16_t data_length_words, void *buffer); int fds_delete_sync(uint16_t id, uint16_t key); bool fds_is_exists(uint16_t id, uint16_t key); diff --git a/firmware/application/src/utils/netdata.h b/firmware/application/src/utils/netdata.h index b9451ff5..37390f1d 100644 --- a/firmware/application/src/utils/netdata.h +++ b/firmware/application/src/utils/netdata.h @@ -3,21 +3,7 @@ #include #include - -#define PACKED __attribute__((packed)) - -#ifndef ARRAYLEN -# define ARRAYLEN(x) (sizeof(x)/sizeof((x)[0])) -#endif - -// nrf52840 platform is little endian -#define U16HTONS(x) ((uint16_t)((((x) & (uint16_t)0x00ffU) << 8) | (((x) & (uint16_t)0xff00U) >> 8))) -#define U16NTOHS(x) U16HTONS(x) -#define U32HTONL(x) ((((x) & (uint32_t)0x000000ffUL) << 24) | \ - (((x) & (uint32_t)0x0000ff00UL) << 8) | \ - (((x) & (uint32_t)0x00ff0000UL) >> 8) | \ - (((x) & (uint32_t)0xff000000UL) >> 24)) -#define U32NTOHL(x) U32HTONL(x) +#include "utils.h" #define NETDATA_MAX_DATA_LENGTH 512 diff --git a/firmware/common/utils.h b/firmware/common/utils.h index 08e8d5ee..dcd367fc 100644 --- a/firmware/common/utils.h +++ b/firmware/common/utils.h @@ -4,4 +4,19 @@ // u32 size align. #define ALIGN_U32 __attribute__((aligned(4))) +#define PACKED __attribute__((packed)) + +#ifndef ARRAYLEN +# define ARRAYLEN(x) (sizeof(x)/sizeof((x)[0])) +#endif + +// nrf52840 platform is little endian +#define U16HTONS(x) ((uint16_t)((((x) & (uint16_t)0x00ffU) << 8) | (((x) & (uint16_t)0xff00U) >> 8))) +#define U16NTOHS(x) U16HTONS(x) +#define U32HTONL(x) ((((x) & (uint32_t)0x000000ffUL) << 24) | \ + (((x) & (uint32_t)0x0000ff00UL) << 8) | \ + (((x) & (uint32_t)0x00ff0000UL) >> 8) | \ + (((x) & (uint32_t)0xff000000UL) >> 24)) +#define U32NTOHL(x) U32HTONL(x) + #endif diff --git a/software/script/chameleon_cli_unit.py b/software/script/chameleon_cli_unit.py index df345c61..70e9b9da 100644 --- a/software/script/chameleon_cli_unit.py +++ b/software/script/chameleon_cli_unit.py @@ -825,7 +825,7 @@ def on_exec(self, args: argparse.Namespace): selected_slot = self.cmd.get_active_slot() slot_info = self.cmd.get_slot_info() - tag_type = chameleon_cmd.TagSpecificType(slot_info[selected_slot][0]) + tag_type = chameleon_cmd.TagSpecificType(slot_info[selected_slot]['hf']) if tag_type == chameleon_cmd.TagSpecificType.TAG_TYPE_MIFARE_Mini: block_count = 20 elif tag_type == chameleon_cmd.TagSpecificType.TAG_TYPE_MIFARE_1024: @@ -1055,12 +1055,12 @@ def args_parser(self) -> ArgumentParserNoExit or None: def get_slot_name(self, slot, sense): try: name = self.cmd.get_slot_tag_nick(slot, sense).decode(encoding="utf8") - return len(name), len(CC+C0), f'{CC}{name}{C0}' + return {'baselen':len(name), 'metalen':len(CC+C0), 'name':f'{CC}{name}{C0}'} except UnexpectedResponseError: - return 0, 0, '' + return {'baselen':0, 'metalen':0, 'name':f''} except UnicodeDecodeError: name = "UTF8 Err" - return len(name), len(CR+C0), f'{CR}{name}{C0}' + return {'baselen':len(name), 'metalen':len(CC+C0), 'name':f'{CC}{name}{C0}'} # hw slot list def on_exec(self, args: argparse.Namespace): @@ -1073,21 +1073,24 @@ def on_exec(self, args: argparse.Namespace): for slot in chameleon_cmd.SlotNumber: hfn = self.get_slot_name(slot, chameleon_cmd.TagSenseType.TAG_SENSE_HF) lfn = self.get_slot_name(slot, chameleon_cmd.TagSenseType.TAG_SENSE_LF) - m = max(hfn[0], lfn[0]) + m = max(hfn['baselen'], lfn['baselen']) maxnamelength = m if m > maxnamelength else maxnamelength - slotnames.append((hfn, lfn)) + slotnames.append({'hf':hfn, 'lf':lfn}) for slot in chameleon_cmd.SlotNumber: fwslot = chameleon_cmd.SlotNumber.to_fw(slot) - hf_tag_type = chameleon_cmd.TagSpecificType(slotinfo[fwslot][0]) - lf_tag_type = chameleon_cmd.TagSpecificType(slotinfo[fwslot][1]) + hf_tag_type = chameleon_cmd.TagSpecificType(slotinfo[fwslot]['hf']) + lf_tag_type = chameleon_cmd.TagSpecificType(slotinfo[fwslot]['lf']) print(f' - {f"Slot {slot}:":{4+maxnamelength+1}}' - f'{f"({CG}active{C0})" if slot == selected else ""}' - f'{f"({CR}disabled{C0}) " if not enabled[fwslot] else ""}') + f'{f"({CG}active{C0})" if slot == selected else ""}') print(f' HF: ' - f'{(slotnames[fwslot][0][2] if args.extend else ""):{maxnamelength+slotnames[fwslot][0][1]+1 if args.extend else maxnamelength+1}}' - f'{f"{CY if enabled[fwslot] else C0}{hf_tag_type}{C0}" if hf_tag_type != chameleon_cmd.TagSpecificType.TAG_TYPE_UNKNOWN else "undef"}') + f'{(slotnames[fwslot]["hf"]["name"] if args.extend else ""):{maxnamelength+slotnames[fwslot]["hf"]["metalen"]+1 if args.extend else maxnamelength+1}}', end='') + print(f'{f"({CR}disabled{C0}) " if not enabled[fwslot]["hf"] else ""}', end='') + if hf_tag_type != chameleon_cmd.TagSpecificType.TAG_TYPE_UNDEFINED: + print(f"{CY if enabled[fwslot]['hf'] else C0}{hf_tag_type}{C0}") + else: + print("undef") if args.extend == 1 and \ - enabled[fwslot] and \ + enabled[fwslot]['hf'] and \ slot == selected and \ hf_tag_type in [ chameleon_cmd.TagSpecificType.TAG_TYPE_MIFARE_Mini, @@ -1107,9 +1110,12 @@ def on_exec(self, args: argparse.Namespace): f' {"Use anti-collision data from block 0:":40}{f"{CG}enabled{C0}" if config["block_anti_coll_mode"] else f"{CR}disabled{C0}"}') print(f' {"Write mode:":40}{CY}{chameleon_cmd.MifareClassicWriteMode(config["write_mode"])}{C0}') print(f' LF: ' - f'{(slotnames[fwslot][1][2] if args.extend else ""):{maxnamelength+slotnames[fwslot][1][1]+1 if args.extend else maxnamelength+1}}' - f'{f"{CY if enabled[fwslot] else C0}{lf_tag_type}{C0}" if lf_tag_type != chameleon_cmd.TagSpecificType.TAG_TYPE_UNKNOWN else "undef"}') - + f'{(slotnames[fwslot]["lf"]["name"] if args.extend else ""):{maxnamelength+slotnames[fwslot]["lf"]["metalen"]+1 if args.extend else maxnamelength+1}}', end='') + print(f'{f"({CR}disabled{C0}) " if not enabled[fwslot]["lf"] else ""}', end='') + if lf_tag_type != chameleon_cmd.TagSpecificType.TAG_TYPE_UNDEFINED: + print(f"{CY if enabled[fwslot]['lf'] else C0}{lf_tag_type}{C0}") + else: + print("undef") @hw_slot.command('change', 'Set emulation tag slot activated.') class HWSlotSet(SlotIndexRequireUnit): @@ -1129,13 +1135,11 @@ class TagTypeRequiredUnit(DeviceRequiredUnit): def add_type_args(parser: ArgumentParserNoExit): type_choices = chameleon_cmd.TagSpecificType.list() help_str = "" - for t in chameleon_cmd.TagSpecificType: - if t == chameleon_cmd.TagSpecificType.TAG_TYPE_UNKNOWN: - continue + for t in type_choices: help_str += f"{t.value} = {t}, " help_str = help_str[:-2] parser.add_argument('-t', "--type", type=int, required=True, help=help_str, metavar="number", - choices=type_choices) + choices=[t.value for t in type_choices]) return parser def args_parser(self) -> ArgumentParserNoExit or None: @@ -1194,20 +1198,21 @@ def on_exec(self, args: argparse.Namespace): @hw_slot.command('enable', 'Set emulation tag slot enable or disable') -class HWSlotEnableSet(SlotIndexRequireUnit): +class HWSlotEnableSet(SlotIndexRequireUnit, SenseTypeRequireUnit): def args_parser(self) -> ArgumentParserNoExit or None: parser = ArgumentParserNoExit() self.add_slot_args(parser) + self.add_sense_type_args(parser) parser.add_argument('-e', '--enable', type=int, required=True, help="1 is Enable or 0 Disable", choices=[0, 1]) return parser - # hw slot enable -s 1 -e 0 + # hw slot enable -s 1 -st 0 -e 0 def on_exec(self, args: argparse.Namespace): slot_num = args.slot + sense_type = args.sense_type enable = args.enable - self.cmd.set_slot_enable(slot_num, enable) - print(f' - Set slot {slot_num} {"enable" if enable else "disable"} success.') - + self.cmd.set_slot_enable(slot_num, sense_type, enable) + print(f' - Set slot {slot_num} {"LF" if sense_type==chameleon_cmd.TagSenseType.TAG_SENSE_LF else "HF"} {"enable" if enable else "disable"} success.') @lf_em_sim.command('set', 'Set simulated em410x card id') class LFEMSimSet(LFEMCardRequiredUnit): @@ -1303,7 +1308,8 @@ def on_exec(self, args: argparse.Namespace): self.cmd.set_slot_data_default(slot, hf_type) self.cmd.set_slot_data_default(slot, lf_type) # finally, we can enable this slot. - self.cmd.set_slot_enable(slot, True) + self.cmd.set_slot_enable(slot, chameleon_cmd.TagSenseType.TAG_SENSE_HF, True) + self.cmd.set_slot_enable(slot, chameleon_cmd.TagSenseType.TAG_SENSE_LF, True) print(f' Slot {slot} setting done.') # update config and save to flash diff --git a/software/script/chameleon_cmd.py b/software/script/chameleon_cmd.py index c3e12ad2..085b86a4 100644 --- a/software/script/chameleon_cmd.py +++ b/software/script/chameleon_cmd.py @@ -149,29 +149,92 @@ def __str__(self): @enum.unique class TagSpecificType(enum.IntEnum): - # Empty slot - TAG_TYPE_UNKNOWN = 0 - # 125 kHz (id) cards - TAG_TYPE_EM410X = 1 - # Mifare Classic - TAG_TYPE_MIFARE_Mini = 2 - TAG_TYPE_MIFARE_1024 = 3 - TAG_TYPE_MIFARE_2048 = 4 - TAG_TYPE_MIFARE_4096 = 5 - # NTAG - TAG_TYPE_NTAG_213 = 6 - TAG_TYPE_NTAG_215 = 7 - TAG_TYPE_NTAG_216 = 8 + TAG_TYPE_UNDEFINED = 0, + + # old HL/LF common types, slots using these ones need to be migrated first + OLD_TAG_TYPE_EM410X = 1, + OLD_TAG_TYPE_MIFARE_Mini = 2, + OLD_TAG_TYPE_MIFARE_1024 = 3, + OLD_TAG_TYPE_MIFARE_2048 = 4, + OLD_TAG_TYPE_MIFARE_4096 = 5, + OLD_TAG_TYPE_NTAG_213 = 6, + OLD_TAG_TYPE_NTAG_215 = 7, + OLD_TAG_TYPE_NTAG_216 = 8, + OLD_TAG_TYPES_END = 9, + + ###### LF ###### + + #### ASK Tag-Talk-First 100 + # EM410x + TAG_TYPE_EM410X = 100, + # FDX-B + # securakey + # gallagher + # PAC/Stanley + # Presco + # Visa2000 + # Viking + # Noralsy + # Jablotron + + #### FSK Tag-Talk-First 200 + # HID Prox + # ioProx + # AWID + # Paradox + + #### PSK Tag-Talk-First 300 + # Indala + # Keri + # NexWatch + + #### Reader-Talk-First 400 + # T5577 + # EM4x05/4x69 + # EM4x50/4x70 + # Hitag series + + TAG_TYPES_LF_END = 999, + + ###### HF ###### + + # MIFARE Classic series 1000 + TAG_TYPE_MIFARE_Mini = 1000, + TAG_TYPE_MIFARE_1024 = 1001, + TAG_TYPE_MIFARE_2048 = 1002, + TAG_TYPE_MIFARE_4096 = 1003, + # MFUL / NTAG series 1100 + TAG_TYPE_NTAG_213 = 1100, + TAG_TYPE_NTAG_215 = 1101, + TAG_TYPE_NTAG_216 = 1102, + # MIFARE Plus series 1200 + # DESFire series 1300 + + # ST25TA series 2000 + + # HF14A-4 series 3000 @staticmethod - def list(exclude_unknown=True): - enum_list = list(map(int, TagSpecificType)) - if exclude_unknown: - enum_list.remove(TagSpecificType.TAG_TYPE_UNKNOWN) - return enum_list + def list(exclude_meta=True): + return [t for t in TagSpecificType + if (t > TagSpecificType.OLD_TAG_TYPES_END and + t != TagSpecificType.TAG_TYPES_LF_END) + or not exclude_meta] + + @staticmethod + def list_hf(): + return [t for t in TagSpecificType.list() + if (t > TagSpecificType.TAG_TYPES_LF_END)] + + @staticmethod + def list_lf(): + return [t for t in TagSpecificType.list() + if (TagSpecificType.TAG_TYPE_UNDEFINED < t < TagSpecificType.TAG_TYPES_LF_END)] def __str__(self): - if self == TagSpecificType.TAG_TYPE_EM410X: + if self == TagSpecificType.TAG_TYPE_UNDEFINED: + return "Undefined" + elif self == TagSpecificType.TAG_TYPE_EM410X: return "EM410X" elif self == TagSpecificType.TAG_TYPE_MIFARE_Mini: return "Mifare Mini" @@ -187,7 +250,9 @@ def __str__(self): return "NTAG 215" elif self == TagSpecificType.TAG_TYPE_NTAG_216: return "NTAG 216" - return "Undefined" + elif self < TagSpecificType.OLD_TAG_TYPES_END: + return "Old tag type, must be migrated! Upgrade fw!" + return "Invalid" @enum.unique @@ -648,8 +713,8 @@ def get_slot_info(self): """ resp = self.device.send_cmd_sync(DATA_CMD_GET_SLOT_INFO) if resp.status == chameleon_status.Device.STATUS_DEVICE_SUCCESS: - resp.data = [struct.unpack_from('!HH', resp.data, i) - for i in range(0, len(resp.data), struct.calcsize('!HH'))] + resp.data = [{'hf': hf, 'lf': lf} + for hf, lf in struct.iter_unpack('!HH', resp.data)] return resp @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) @@ -713,7 +778,7 @@ def set_slot_data_default(self, slot_index: SlotNumber, tag_type: TagSpecificTyp return self.device.send_cmd_sync(DATA_CMD_SET_SLOT_DATA_DEFAULT, data) @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) - def set_slot_enable(self, slot_index: SlotNumber, enabled: bool): + def set_slot_enable(self, slot_index: SlotNumber, sense_type: TagSenseType, enabled: bool): """ Set whether the specified card slot is enabled :param slot_index: Card slot number @@ -721,7 +786,7 @@ def set_slot_enable(self, slot_index: SlotNumber, enabled: bool): :return: """ # SlotNumber() will raise error for us if slot_index not in slot range - data = struct.pack('!BB', SlotNumber.to_fw(slot_index), enabled) + data = struct.pack('!BBB', SlotNumber.to_fw(slot_index), sense_type, enabled) return self.device.send_cmd_sync(DATA_CMD_SET_SLOT_ENABLE, data) @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) @@ -933,7 +998,10 @@ def get_enabled_slots(self): """ Get enabled slots """ - return self.device.send_cmd_sync(DATA_CMD_GET_ENABLED_SLOTS) + resp = self.device.send_cmd_sync(DATA_CMD_GET_ENABLED_SLOTS) + if resp.status == chameleon_status.Device.STATUS_DEVICE_SUCCESS: + resp.data = [{'hf': hf, 'lf': lf} for hf, lf in struct.iter_unpack('!BB', resp.data)] + return resp @expect_response(chameleon_status.Device.STATUS_DEVICE_SUCCESS) def set_animation_mode(self, value: int): From 25f7a919415f23e6a541e6ace872d462aa0990ad Mon Sep 17 00:00:00 2001 From: Philippe Teuwen Date: Sat, 23 Sep 2023 21:38:17 +0200 Subject: [PATCH 23/38] fix changelog --- CHANGELOG.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cd2845ec..2c378812 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,8 +3,6 @@ All notable changes to this project will be documented in this file. This project uses the changelog in accordance with [keepchangelog](http://keepachangelog.com/). Please use this to write notable changes, which is not the same as git commit log... ## [unreleased][unreleased] - - slot_number|sense_type|enable -Disruptive changes: - Changed slot enabled logic: now we have separate enabled_hf and enabled_lf, changed GET_ENABLED_SLOTS and SET_SLOT_ENABLE (@doegox) - Changed tag type enum to be ready for new types, changed stored slotConfig and GET_SLOT_INFO (@doegox) - Removed MF1_DETECT_DARKSIDE (@doegox) From 234b22c48a7555334b613d53e5bd320d0733e72d Mon Sep 17 00:00:00 2001 From: Philippe Teuwen Date: Sun, 24 Sep 2023 00:32:48 +0200 Subject: [PATCH 24/38] use field LED also in reader mode --- firmware/application/src/rfid/reader/hf/rc522.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/firmware/application/src/rfid/reader/hf/rc522.c b/firmware/application/src/rfid/reader/hf/rc522.c index 1bec25cc..a95b68df 100644 --- a/firmware/application/src/rfid/reader/hf/rc522.c +++ b/firmware/application/src/rfid/reader/hf/rc522.c @@ -24,6 +24,7 @@ NRF_LOG_MODULE_REGISTER(); #define RC522_DOSEL nrf_gpio_pin_clear(HF_SPI_SELECT) #define RC522_UNSEL nrf_gpio_pin_set(HF_SPI_SELECT) +bool g_is_reader_antenna_on = false; //CRC 14A calculator, when the MCU performance is too weak, or when the MCU is busy, you can use 522 to calculate CRC static uint8_t m_crc_computer = 0; @@ -971,6 +972,8 @@ void pcd_14a_reader_calc_crc(uint8_t *pbtData, size_t szLen, uint8_t *pbtCrc) { */ inline void pcd_14a_reader_antenna_on(void) { set_register_mask(TxControlReg, 0x03); + g_is_reader_antenna_on = true; + TAG_FIELD_LED_ON(); } /** @@ -978,6 +981,8 @@ inline void pcd_14a_reader_antenna_on(void) { */ inline void pcd_14a_reader_antenna_off(void) { clear_register_mask(TxControlReg, 0x03); + g_is_reader_antenna_on = false; + TAG_FIELD_LED_OFF(); } /** From f7db6d0fb3a65006ac3a6eeaccb75e2610cf6b41 Mon Sep 17 00:00:00 2001 From: Philippe Teuwen Date: Sun, 24 Sep 2023 00:42:58 +0200 Subject: [PATCH 25/38] hw 14a raw: closer to pm3 syntax, removed bit_frame,... Now data length is always in bits Option -o => -a and only needed to turn field on without select or data Reorganize pcd_14a_reader_raw_cmd Some more checks --- docs/protocol.md | 12 + firmware/application/src/app_cmd.c | 17 +- .../application/src/rfid/reader/hf/rc522.c | 208 +++++++++--------- .../application/src/rfid/reader/hf/rc522.h | 4 +- software/script/chameleon_cli_unit.py | 38 ++-- software/script/chameleon_cmd.py | 37 ++-- 6 files changed, 166 insertions(+), 150 deletions(-) diff --git a/docs/protocol.md b/docs/protocol.md index 018d477b..85b4f1ab 100644 --- a/docs/protocol.md +++ b/docs/protocol.md @@ -251,6 +251,17 @@ Notes: * Command: 24 bytes: `type|block|key[6]|block_data[16]`. Key as 6 bytes. * Response: no data * CLI: cf `hf mf wrbl` +### 2010: HF14A_RAW +* Command: : 5+N bytes: `options|resp_timeout_ms[2]|bitlen[2]` followed by data to be transmitted, with `options` a 1-byte BigEndian bitfield, so starting from MSB: + * `activate_rf_field`:1 + * `wait_response`:1 + * `append_crc`:1 + * `auto_select`:1 + * `keep_rf_field`:1 + * `check_response_crc`:1 + * `reserved`:2 +* Response: data sent by the card +* CLI: cf `hf 14a raw` ### 3000: EM410X_SCAN * Command: no data * Response: 5 bytes. `id[5]`. ID as 5 bytes. @@ -356,6 +367,7 @@ Be verbose, explicit and reuse conventions, in order to enhance code maintainabi ### Guideline: Structs - Define C `struct` for cmd/resp data greater than a single byte, use and abuse of `struct.pack`/`struct.unpack` in Python. So one can understand the payload format at a simple glimpse. Exceptions to `C` struct are when the formats are of variable length (but Python `struct` is still flexible enough to cope with such formats!) - Avoid hardcoding offsets, use `sizeof()`, `offsetof(struct, field)` in C and `struct.calcsize()` in Python +- For complex bitfield structs, exceptionally you can use ctypes in Python. Beware ctypes.BigEndianStructure bitfield will be parsed in the firmware in the reverse order, from LSB to MSB. ### Guideline: Status If single byte of data to return, still use a 1-byte `data`, not `status`. Standard response status is `STATUS_DEVICE_SUCCESS` for general commands, `HF_TAG_OK` for HF commands and `LF_TAG_OK` for LF commands. If the response status is different than those, the response data is empty. Response status are generic and cover things like tag disappearance or tag non-conformities with the ISO standard. If a command needs more specific response status, it is added in the first byte of the data, to avoid cluttering the 1-byte general status enum with command-specific statuses. See e.g. [MF1_DARKSIDE_ACQUIRE](#2004-mf1_darkside_acquire). ### Guideline: unambiguous types diff --git a/firmware/application/src/app_cmd.c b/firmware/application/src/app_cmd.c index b3879e65..d78199ad 100644 --- a/firmware/application/src/app_cmd.c +++ b/firmware/application/src/app_cmd.c @@ -404,21 +404,20 @@ static data_frame_tx_t *cmd_processor_hf14a_raw(uint16_t cmd, uint16_t status, u uint16_t resp_length = 0; typedef struct { - struct { // MSB -> LSB - uint8_t reserved : 1; + struct { // LSB -> MSB + uint8_t reserved : 2; uint8_t check_response_crc : 1; uint8_t keep_rf_field : 1; uint8_t auto_select : 1; - uint8_t bit_frame : 1; uint8_t append_crc : 1; uint8_t wait_response : 1; - uint8_t open_rf_field : 1; + uint8_t activate_rf_field : 1; } options; // U16NTOHS uint16_t resp_timeout; - uint16_t data_length; + uint16_t data_bitlength; uint8_t data_buffer[0]; // We can have a lot of data or no data. struct just to compute offsets with min options. } PACKED payload_t; @@ -427,27 +426,25 @@ static data_frame_tx_t *cmd_processor_hf14a_raw(uint16_t cmd, uint16_t status, u return data_frame_make(cmd, STATUS_PAR_ERR, 0, NULL); } - NRF_LOG_INFO("open_rf_field = %d", payload->options.open_rf_field); + NRF_LOG_INFO("activate_rf_field = %d", payload->options.activate_rf_field); NRF_LOG_INFO("wait_response = %d", payload->options.wait_response); NRF_LOG_INFO("append_crc = %d", payload->options.append_crc); - NRF_LOG_INFO("bit_frame = %d", payload->options.bit_frame); NRF_LOG_INFO("auto_select = %d", payload->options.auto_select); NRF_LOG_INFO("keep_rf_field = %d", payload->options.keep_rf_field); NRF_LOG_INFO("check_response_crc = %d", payload->options.check_response_crc); NRF_LOG_INFO("reserved = %d", payload->options.reserved); status = pcd_14a_reader_raw_cmd( - payload->options.open_rf_field, + payload->options.activate_rf_field, payload->options.wait_response, payload->options.append_crc, - payload->options.bit_frame, payload->options.auto_select, payload->options.keep_rf_field, payload->options.check_response_crc, U16NTOHS(payload->resp_timeout), - U16NTOHS(payload->data_length), + U16NTOHS(payload->data_bitlength), payload->data_buffer, resp, diff --git a/firmware/application/src/rfid/reader/hf/rc522.c b/firmware/application/src/rfid/reader/hf/rc522.c index a95b68df..569ba160 100644 --- a/firmware/application/src/rfid/reader/hf/rc522.c +++ b/firmware/application/src/rfid/reader/hf/rc522.c @@ -1132,7 +1132,6 @@ inline void pcd_14a_reader_crc_computer(uint8_t use522CalcCRC) { * @brief : The hf 14a raw command implementation function can be used to send the 14A command with the specified configuration parameters. * @param :waitResp : Wait for tag response * @param :appendCrc : Do you want to add CRC before sending -* @param :bitsFrame : Is it necessary to send bit frames, which are mutually exclusive with the appendCrc parameter. * @param :autoSelect : Automatically select card before sending data * @param :keepField : Do you want to keep the RF field on after sending * @param :checkCrc : Is CRC verified after receiving data? If CRC verification is enabled, CRC bytes will be automatically removed after verification is completed. @@ -1143,122 +1142,125 @@ inline void pcd_14a_reader_crc_computer(uint8_t use522CalcCRC) { * @retval : Execution Status * */ -uint8_t pcd_14a_reader_raw_cmd(bool openRFField, bool waitResp, bool appendCrc, bool bitsFrame, bool autoSelect, bool keepField, bool checkCrc, uint16_t waitRespTimeout, - uint16_t szDataSend, uint8_t *pDataSend, uint8_t *pDataRecv, uint16_t *pszDataRecv, uint16_t szDataRecvBitMax) { - // Status code, default is OK. - uint8_t status = HF_TAG_OK; - // Reset recv length. - *pszDataRecv = 0; - // Old response timeout. - uint16_t oldWaitRespTimeout; - - if (openRFField) { // Open rf filed? +uint8_t pcd_14a_reader_raw_cmd(bool openRFField, bool waitResp, bool appendCrc, bool autoSelect, bool keepField, bool checkCrc, uint16_t waitRespTimeout, + uint16_t szDataSendBits, uint8_t *pDataSend, uint8_t *pDataRecv, uint16_t *pszDataRecv, uint16_t szDataRecvBitMax) { + // Status code, default is OK. + uint8_t status = HF_TAG_OK; + // Reset recv length. + *pszDataRecv = 0; + + // If additional CRC is required, first add the CRC to the tail. + if (appendCrc) { + if (szDataSendBits == 0) { + NRF_LOG_INFO("Adding CRC but missing data"); + return STATUS_PAR_ERR; + } + if (szDataSendBits % 8) { + NRF_LOG_INFO("Adding CRC incompatible with partial bytes"); + return STATUS_PAR_ERR; + } + if (szDataSendBits > ((DEF_FIFO_LENGTH - DEF_CRC_LENGTH) * 8)) { + // Note: Adding CRC requires at least two bytes of free space. If the transmitted data is already greater than or equal to 64, an error needs to be returned + NRF_LOG_INFO("Adding CRC requires data length less than or equal to 62."); + return STATUS_PAR_ERR; + } + // Calculate and append CRC byte data to the buffer + crc_14a_append(pDataSend, szDataSendBits / 8); + // CRC is also sent as part of the data, so the total length needs to be added to the CRC length here + szDataSendBits += DEF_CRC_LENGTH * 8; + } + + if (autoSelect || szDataSendBits) { + // override openRFField if we need to select or to send data + openRFField = true; + } + if (openRFField && ! g_is_reader_antenna_on) { // Open rf field? + pcd_14a_reader_reset(); pcd_14a_reader_antenna_on(); bsp_delay_ms(8); } + if (autoSelect) { + picc_14a_tag_t ti; + status = pcd_14a_reader_scan_once(&ti); + // Determine whether the card search was successful + if (status != HF_TAG_OK) { + pcd_14a_reader_antenna_off(); + return status; + } + } + // Is there any data that needs to be sent - if (szDataSend > 0) { - // If additional CRC is required, first add the CRC to the tail. - if (appendCrc) { - // Note: Adding CRC requires at least two bytes of free space. If the transmitted data is already greater than or equal to 64, an error needs to be returned - if (szDataSend > (DEF_FIFO_LENGTH - DEF_CRC_LENGTH)) { - NRF_LOG_INFO("Adding CRC requires data length less than or equal to 62."); - status = STATUS_PAR_ERR; - } else { - // Calculate and append CRC byte data to the buffer - crc_14a_append(pDataSend, szDataSend); - // CRC is also sent as part of the data, so the total length needs to be added to the CRC length here - szDataSend += DEF_CRC_LENGTH; - } + if (szDataSendBits) { + // If there is no need to receive data, the data receiving cache needs to be empty, otherwise a specified timeout value needs to be set + // Caching old timeout values + uint16_t oldWaitRespTimeout = g_com_timeout_ms; + if (waitResp) { + // Then set the new values in + g_com_timeout_ms = waitRespTimeout; + } else { + pDataRecv = NULL; + } + if (szDataSendBits % 8) { + status = pcd_14a_reader_bits_transfer( + pDataSend, + szDataSendBits, + NULL, + pDataRecv, + NULL, + pszDataRecv, + szDataRecvBitMax + ); + } else { + status = pcd_14a_reader_bytes_transfer( + PCD_TRANSCEIVE, + pDataSend, + szDataSendBits / 8, + pDataRecv, + pszDataRecv, + szDataRecvBitMax + ); } - // Ensure that the previous operation is normal - if (status == HF_TAG_OK) { - // Determine if card selection is necessary first based on needs - if (autoSelect) { - picc_14a_tag_t ti; - status = pcd_14a_reader_scan_once(&ti); - // Determine whether the card search was successful - if (status != HF_TAG_OK) { - return status; - } - } - - // If there is no need to receive data, the data receiving cache needs to be empty, otherwise a specified timeout value needs to be set - if (waitResp) { - // Caching old timeout values - oldWaitRespTimeout = g_com_timeout_ms; - // Then set the new values in - g_com_timeout_ms = waitRespTimeout; - } else { - pDataRecv = NULL; - } - - // Ensure that the previous operation is normal - if (status == HF_TAG_OK) { - if (bitsFrame) { - status = pcd_14a_reader_bits_transfer( - pDataSend, - szDataSend, - NULL, - pDataRecv, - NULL, - pszDataRecv, - szDataRecvBitMax - ); - } else { - status = pcd_14a_reader_bytes_transfer( - PCD_TRANSCEIVE, - pDataSend, - szDataSend, - pDataRecv, - pszDataRecv, - szDataRecvBitMax - ); - } - } - - // If we need to receive data, we need to perform further operations on the data based on the remaining configuration after receiving it - if (waitResp) { - // Number of bits to bytes - uint8_t finalRecvBytes = (*pszDataRecv / 8) + (*pszDataRecv % 8 > 0 ? 1 : 0); - // If CRC verification is required, we need to perform CRC calculation - if (checkCrc) { - if (finalRecvBytes >= 3) { // Ensure at least three bytes (one byte of data+two bytes of CRC) - // Calculate and store CRC - uint8_t crc_buff[DEF_CRC_LENGTH] = { 0x00 }; - crc_14a_calculate(pDataRecv, finalRecvBytes - DEF_CRC_LENGTH, crc_buff); - // Verify CRC - if (pDataRecv[finalRecvBytes - 2] != crc_buff[0] || pDataRecv[finalRecvBytes - 1] != crc_buff[1]) { - // We have found an error in CRC verification and need to inform the upper computer! - *pszDataRecv = 0; - status = HF_ERR_CRC; - } else { - // If the CRC needs to be verified by the device and the device determines that the CRC is normal, - // we will return the data without CRC - *pszDataRecv = finalRecvBytes - DEF_CRC_LENGTH; - } - } else { - // The data is insufficient to support the length of the CRC, so it is returned as is + // If we need to receive data, we need to perform further operations on the data based on the remaining configuration after receiving it + if (waitResp) { + // Number of bits to bytes + uint8_t finalRecvBytes = (*pszDataRecv / 8) + (*pszDataRecv % 8 > 0 ? 1 : 0); + // If CRC verification is required, we need to perform CRC calculation + if (checkCrc) { + if (finalRecvBytes >= 3) { // Ensure at least three bytes (one byte of data+two bytes of CRC) + // Calculate and store CRC + uint8_t crc_buff[DEF_CRC_LENGTH] = { 0x00 }; + crc_14a_calculate(pDataRecv, finalRecvBytes - DEF_CRC_LENGTH, crc_buff); + // Verify CRC + if (pDataRecv[finalRecvBytes - 2] != crc_buff[0] || pDataRecv[finalRecvBytes - 1] != crc_buff[1]) { + // We have found an error in CRC verification and need to inform the upper computer! *pszDataRecv = 0; + status = HF_ERR_CRC; + } else { + // If the CRC needs to be verified by the device and the device determines that the CRC is normal, + // we will return the data without CRC + *pszDataRecv = finalRecvBytes - DEF_CRC_LENGTH; } } else { - // Do not verify CRC, all data is returned as is - *pszDataRecv = finalRecvBytes; + // The data is insufficient to support the length of the CRC, so it is returned as is + *pszDataRecv = 0; } - // We need to recover the timeout value - g_com_timeout_ms = oldWaitRespTimeout; } else { - *pszDataRecv = 0; + // Do not verify CRC, all data is returned as is + *pszDataRecv = finalRecvBytes; } + // We need to recover the timeout value + g_com_timeout_ms = oldWaitRespTimeout; + } else { + *pszDataRecv = 0; } } - - // Finally, keep the field open as needed - if (!keepField) { - pcd_14a_reader_antenna_off(); - } - return status; + // Finally, keep the field open as needed + if (!keepField) { + pcd_14a_reader_antenna_off(); + } + + return status; } \ No newline at end of file diff --git a/firmware/application/src/rfid/reader/hf/rc522.h b/firmware/application/src/rfid/reader/hf/rc522.h index 11926ab3..899c1485 100644 --- a/firmware/application/src/rfid/reader/hf/rc522.h +++ b/firmware/application/src/rfid/reader/hf/rc522.h @@ -220,8 +220,8 @@ uint8_t pcd_14a_reader_mf1_read(uint8_t addr, uint8_t *pData); uint8_t pcd_14a_reader_halt_tag(void); void pcd_14a_reader_fast_halt_tag(void); -uint8_t pcd_14a_reader_raw_cmd(bool openRFField, bool waitResp, bool appendCrc, bool bitsFrame, bool autoSelect, bool keepField, bool checkCrc, uint16_t waitRespTimeout, - uint16_t szDataSend, uint8_t* pDataSend, uint8_t* pDataRecv, uint16_t* pszDataRecv, uint16_t szDataRecvBitMax); +uint8_t pcd_14a_reader_raw_cmd(bool openRFField, bool waitResp, bool appendCrc, bool autoSelect, bool keepField, bool checkCrc, uint16_t waitRespTimeout, + uint16_t szDataSendBits, uint8_t* pDataSend, uint8_t* pDataRecv, uint16_t* pszDataRecv, uint16_t szDataRecvBitMax); // UID & UFUID tag operation uint8_t pcd_14a_reader_gen1a_unlock(void); diff --git a/software/script/chameleon_cli_unit.py b/software/script/chameleon_cli_unit.py index 70e9b9da..cdc1555e 100644 --- a/software/script/chameleon_cli_unit.py +++ b/software/script/chameleon_cli_unit.py @@ -1589,26 +1589,34 @@ def bool_to_bit(self, value): def args_parser(self) -> ArgumentParserNoExit or None: parser = ArgumentParserNoExit() - parser.add_argument('-r', '--response', help="do not read response", action='store_true', default=False,) - parser.add_argument('-c', '--crc', help="calculate and append CRC", action='store_true', default=False,) - parser.add_argument('-cc', '--crc-clear', help="Verify and clear CRC of received data", action='store_true', default=False,) - parser.add_argument('-k', '--keep-rf', help="keep signal field ON after receive", action='store_true', default=False,) - parser.add_argument('-o', '--open-rf', help="active signal field ON", action='store_true', default=False,) - parser.add_argument('-s', '--select-tag', help="Select the tag before executing the command", action='store_true', default=False,) - parser.add_argument('-b', '--bits', type=int, help="number of bits to send. Useful for send partial byte") - parser.add_argument('-t', '--timeout', type=int, help="timeout in ms", default=100) + parser.add_argument('-a', '--activate-rf', help="Active signal field ON without select", action='store_true', default=False,) + parser.add_argument('-s', '--select-tag', help="Active signal field ON with select", action='store_true', default=False,) + # TODO: parser.add_argument('-3', '--type3-select-tag', help="Active signal field ON with ISO14443-3 select (no RATS)", action='store_true', default=False,) parser.add_argument('-d', '--data', type=str, help="Data to be sent") + parser.add_argument('-b', '--bits', type=int, help="Number of bits to send. Useful for send partial byte") + parser.add_argument('-c', '--crc', help="Calculate and append CRC", action='store_true', default=False,) + parser.add_argument('-r', '--response', help="Do not read response", action='store_true', default=False,) + parser.add_argument('-cc', '--crc-clear', help="Verify and clear CRC of received data", action='store_true', default=False,) + parser.add_argument('-k', '--keep-rf', help="Keep signal field ON after receive", action='store_true', default=False,) + parser.add_argument('-t', '--timeout', type=int, help="Timeout in ms", default=100) + # TODO: need support for carriage returns in parser, why are they mangled? + # parser.description = 'Examples:\n' \ + # ' hf 14a raw -b 7 -d 40 -k\n' \ + # ' hf 14a raw -d 43 -k\n' \ + # ' hf 14a raw -d 3000 -c\n' \ + # ' hf 14a raw -sc -d 6000\n' return parser + def on_exec(self, args: argparse.Namespace): options = { - 'open_rf_field': self.bool_to_bit(args.open_rf), - 'wait_response': self.bool_to_bit(args.response == False), + 'activate_rf_field': self.bool_to_bit(args.activate_rf), + 'wait_response': self.bool_to_bit(not args.response), 'append_crc': self.bool_to_bit(args.crc), - 'bit_frame': self.bool_to_bit(args.bits is not None), 'auto_select': self.bool_to_bit(args.select_tag), 'keep_rf_field': self.bool_to_bit(args.keep_rf), 'check_response_crc': self.bool_to_bit(args.crc_clear), + #'auto_type3_select': self.bool_to_bit(args.type3-select-tag), } data: str = args.data if data is not None: @@ -1624,6 +1632,10 @@ def on_exec(self, args: argparse.Namespace): return else: data_bytes = [] + if args.bits is not None and args.crc: + print(f" [!] {CR}--bits and --crc are mutually exclusive{C0}") + return + # Exec 14a raw cmd. resp = self.cmd.hf14a_raw(options, args.timeout, data_bytes, args.bits) if resp.status == chameleon_status.Device.HF_TAG_OK: @@ -1632,9 +1644,9 @@ def on_exec(self, args: argparse.Namespace): # print head " - " + # print data - ' '.join([ hex(byte).replace('0x', '').rjust(2, '0') for byte in resp.data ]) + ' '.join([hex(byte).replace('0x', '').rjust(2, '0') for byte in resp.data]) ) else: - print(F" [*] {CY}No data response{C0}") + print(F" [*] {CY}No response{C0}") else: print(f" [!] {CR}{chameleon_status.message[resp.status]}{C0} ") diff --git a/software/script/chameleon_cmd.py b/software/script/chameleon_cmd.py index 085b86a4..367e78f6 100644 --- a/software/script/chameleon_cmd.py +++ b/software/script/chameleon_cmd.py @@ -617,7 +617,7 @@ def mf1_write_one_block(self, block, type_value, key, block_data): resp.data = resp.status == chameleon_status.Device.HF_TAG_OK return resp - def hf14a_raw(self, options, resp_timeout_ms=100, data=[], bit_owned_by_the_last_byte=None): + def hf14a_raw(self, options, resp_timeout_ms=100, data=[], bitlen=None): """ Send raw cmd to 14a tag :param options: @@ -629,40 +629,35 @@ def hf14a_raw(self, options, resp_timeout_ms=100, data=[], bit_owned_by_the_last class CStruct(ctypes.BigEndianStructure): _fields_ = [ - ("open_rf_field", ctypes.c_uint8, 1), + ("activate_rf_field", ctypes.c_uint8, 1), ("wait_response", ctypes.c_uint8, 1), ("append_crc", ctypes.c_uint8, 1), - ("bit_frame", ctypes.c_uint8, 1), ("auto_select", ctypes.c_uint8, 1), ("keep_rf_field", ctypes.c_uint8, 1), ("check_response_crc", ctypes.c_uint8, 1), - ("reserved", ctypes.c_uint8, 1), + ("reserved", ctypes.c_uint8, 2), ] cs = CStruct() - cs.open_rf_field = options['open_rf_field'] + cs.activate_rf_field = options['activate_rf_field'] cs.wait_response = options['wait_response'] cs.append_crc = options['append_crc'] - cs.bit_frame = options['bit_frame'] cs.auto_select = options['auto_select'] cs.keep_rf_field = options['keep_rf_field'] cs.check_response_crc = options['check_response_crc'] - if options['bit_frame'] == 1: - bits_or_bytes = len(data) * 8 # bits = bytes * 8(bit) - if bit_owned_by_the_last_byte is not None and bit_owned_by_the_last_byte != 8: - bits_or_bytes = bits_or_bytes - (8 - bit_owned_by_the_last_byte) + if bitlen is None: + bitlen = len(data) * 8 # bits = bytes * 8(bit) else: - bits_or_bytes = len(data) # bytes length - - if len(data) > 0: - data = struct.pack(f'!BHH{len(data)}s', bytes(cs)[0], resp_timeout_ms, bits_or_bytes, bytearray(data)) - else: - data = struct.pack(f'!BHH', bytes(cs)[0], resp_timeout_ms, 0) + if len(data) == 0: + raise ValueError(f'bitlen={bitlen} but missing data') + if not ((len(data) - 1) * 8 < bitlen <= len(data) * 8): + raise ValueError(f'bitlen={bitlen} incompatible with provided data ({len(data)} bytes), ' + f'must be between {((len(data) - 1) * 8 )+1} and {len(data) * 8} included') + data = bytes(cs)+struct.pack(f'!HH{len(data)}s', resp_timeout_ms, bitlen, bytearray(data)) return self.device.send_cmd_sync(DATA_CMD_HF14A_RAW, data, timeout=(resp_timeout_ms / 1000) + 1) - @expect_response(chameleon_status.Device.HF_TAG_OK) def mf1_static_nested_acquire(self, block_known, type_known, key_known, block_target, type_target): """ @@ -1228,26 +1223,24 @@ def test_fn(): cml.set_device_reader_mode() options = { - 'open_rf_field': 1, + 'activate_rf_field': 1, 'wait_response': 1, 'append_crc': 0, - 'bit_frame': 1, 'auto_select': 0, 'keep_rf_field': 1, 'check_response_crc': 0, } # unlock 1 - resp = cml.hf14a_raw(options=options, resp_timeout_ms=1000, data=[0x40], bit_owned_by_the_last_byte=7) + resp = cml.hf14a_raw(options=options, resp_timeout_ms=1000, data=[0x40], bitlen=7) if resp.status == 0x00 and resp.data[0] == 0x0a: print("Gen1A unlock 1 success") # unlock 2 - options['bit_frame'] = 0 resp = cml.hf14a_raw(options=options, resp_timeout_ms=1000, data=[0x43]) if resp.status == 0x00 and resp.data[0] == 0x0a: print("Gen1A unlock 2 success") - print("Start dump gen1a memeory...") + print("Start dump gen1a memory...") block = 0 while block < 64: # Tag read block cmd From 867ee7bf9bdfe68ee49e500bc19a3ff373fbae36 Mon Sep 17 00:00:00 2001 From: Philippe Teuwen Date: Sun, 24 Sep 2023 01:19:10 +0200 Subject: [PATCH 26/38] Breaking change: DATA_CMD_GET_DEVICE_MODEL to match chameleon_device_type_t cf https://github.com/RfidResearchGroup/ChameleonUltra/pull/147#pullrequestreview-1637478473 --- docs/protocol.md | 2 +- firmware/application/src/app_cmd.c | 6 +----- software/script/chameleon_cli_unit.py | 4 ++-- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/docs/protocol.md b/docs/protocol.md index 85b4f1ab..c89c1fde 100644 --- a/docs/protocol.md +++ b/docs/protocol.md @@ -166,7 +166,7 @@ In the following list, "CLI" refers to one typical CLI command using the describ * CLI: cf `hw ble bonds clear` ### 1033: GET_DEVICE_MODEL * Command: no data -* Response: 1 byte. `hw_version` aka `NRF_DFU_HW_VERSION` (0=Ultra, 1=Lite) +* Response: 1 byte. `hw_version` aka `NRF_DFU_HW_VERSION` according to `chameleon_device_type_t` enum (0=Ultra, 1=Lite) * CLI: cf `hw version` ### 1034: GET_DEVICE_SETTINGS * Command: no data diff --git a/firmware/application/src/app_cmd.c b/firmware/application/src/app_cmd.c index d78199ad..f9b8ee0d 100644 --- a/firmware/application/src/app_cmd.c +++ b/firmware/application/src/app_cmd.c @@ -49,11 +49,7 @@ static data_frame_tx_t *cmd_processor_get_git_version(uint16_t cmd, uint16_t sta static data_frame_tx_t *cmd_processor_get_device_model(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { -#if defined(PROJECT_CHAMELEON_ULTRA) - uint8_t resp_data = 1; -#else - uint8_t resp_data = 0; -#endif + uint8_t resp_data = hw_get_device_type(); return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, sizeof(resp_data), &resp_data); } diff --git a/software/script/chameleon_cli_unit.py b/software/script/chameleon_cli_unit.py index cdc1555e..63bc65dd 100644 --- a/software/script/chameleon_cli_unit.py +++ b/software/script/chameleon_cli_unit.py @@ -244,7 +244,7 @@ def on_exec(self, args: argparse.Namespace): self.device_com.open(args.port) self.device_com.commands = self.cmd.get_device_capabilities() major, minor = self.cmd.get_app_version() - model = 'Ultra' if self.cmd.get_device_model() else 'Lite' + model = ['Ultra', 'Lite'][self.cmd.get_device_model()] print(f" {{ Chameleon {model} connected: v{major}.{minor} }}") except Exception as e: @@ -306,7 +306,7 @@ def on_exec(self, args: argparse.Namespace): fw_version_tuple = self.cmd.get_app_version() fw_version = f'v{fw_version_tuple[0]}.{fw_version_tuple[1]}' git_version = self.cmd.get_git_version() - model = 'Ultra' if self.cmd.get_device_model() else 'Lite' + model = ['Ultra', 'Lite'][self.cmd.get_device_model()] print(f' - Chameleon {model}, Version: {fw_version} ({git_version})') From 941d42561af900fc1662114bf9f4c4af43a34ae7 Mon Sep 17 00:00:00 2001 From: Philippe Teuwen Date: Sun, 24 Sep 2023 01:46:32 +0200 Subject: [PATCH 27/38] MF1_DETECT_SUPPORT: remove redundant data cf https://github.com/RfidResearchGroup/ChameleonUltra/pull/147#issuecomment-1732338315 --- firmware/application/src/app_cmd.c | 8 ++------ firmware/application/src/rfid/reader/hf/mf1_toolbox.c | 11 +++-------- firmware/application/src/rfid/reader/hf/mf1_toolbox.h | 2 +- software/script/chameleon_cmd.py | 5 +---- 4 files changed, 7 insertions(+), 19 deletions(-) diff --git a/firmware/application/src/app_cmd.c b/firmware/application/src/app_cmd.c index f9b8ee0d..42b0b67b 100644 --- a/firmware/application/src/app_cmd.c +++ b/firmware/application/src/app_cmd.c @@ -235,12 +235,8 @@ static data_frame_tx_t *cmd_processor_hf14a_scan(uint16_t cmd, uint16_t status, } static data_frame_tx_t *cmd_processor_mf1_detect_support(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { - uint8_t support; - status = check_std_mifare_nt_support((bool *)&support); - if (status != HF_TAG_OK) { - return data_frame_make(cmd, status, 0, NULL); - } - return data_frame_make(cmd, HF_TAG_OK, sizeof(support), &support); + status = check_std_mifare_nt_support(); + return data_frame_make(cmd, status, 0, NULL); } static data_frame_tx_t *cmd_processor_mf1_detect_prng(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { diff --git a/firmware/application/src/rfid/reader/hf/mf1_toolbox.c b/firmware/application/src/rfid/reader/hf/mf1_toolbox.c index 473262fc..e8906345 100644 --- a/firmware/application/src/rfid/reader/hf/mf1_toolbox.c +++ b/firmware/application/src/rfid/reader/hf/mf1_toolbox.c @@ -609,7 +609,7 @@ uint8_t check_tag_response_nt(picc_14a_tag_t *tag, uint32_t *nt) { * Lost card hf_tag_no and wrong status hf_errstat * */ -uint8_t check_std_mifare_nt_support(bool *support) { +uint8_t check_std_mifare_nt_support(void) { uint32_t nt1 = 0; // Find card, search on the field @@ -617,13 +617,8 @@ uint8_t check_std_mifare_nt_support(bool *support) { return HF_TAG_NO; } - // Get NT - uint8_t status = check_tag_response_nt(p_tag_info, &nt1); - if (status == HF_TAG_NO) { - return HF_TAG_NO; - } - *support = status == HF_TAG_OK; - return HF_TAG_OK; + // Get NT and return status + return check_tag_response_nt(p_tag_info, &nt1); } /** diff --git a/firmware/application/src/rfid/reader/hf/mf1_toolbox.h b/firmware/application/src/rfid/reader/hf/mf1_toolbox.h index 0f28a26f..48dfb7cd 100644 --- a/firmware/application/src/rfid/reader/hf/mf1_toolbox.h +++ b/firmware/application/src/rfid/reader/hf/mf1_toolbox.h @@ -91,7 +91,7 @@ uint8_t nested_recover_key(NESTED_CORE_PARAM_DEF, mf1_nested_core_t ncs[SETS_NR] uint8_t static_nested_recover_key(NESTED_CORE_PARAM_DEF, mf1_static_nested_core_t *sncs); uint8_t check_prng_type(mf1_prng_type_t *type); -uint8_t check_std_mifare_nt_support(bool *support); +uint8_t check_std_mifare_nt_support(); void antenna_switch_delay(uint32_t delay_ms); uint8_t auth_key_use_522_hw(uint8_t block, uint8_t type, uint8_t *key); diff --git a/software/script/chameleon_cmd.py b/software/script/chameleon_cmd.py index 367e78f6..5417e42b 100644 --- a/software/script/chameleon_cmd.py +++ b/software/script/chameleon_cmd.py @@ -508,16 +508,13 @@ def hf14a_scan(self): resp.data = data return resp - @expect_response(chameleon_status.Device.HF_TAG_OK) def mf1_detect_support(self): """ Detect whether it is mifare classic tag :return: """ resp = self.device.send_cmd_sync(DATA_CMD_MF1_DETECT_SUPPORT) - if resp.status == chameleon_status.Device.HF_TAG_OK: - resp.data, = struct.unpack('!?', resp.data) - return resp + return resp.status == chameleon_status.Device.HF_TAG_OK @expect_response(chameleon_status.Device.HF_TAG_OK) def mf1_detect_prng(self): From 1dc8891e1b3eb122ff657f219393a8aa1ad0ae1c Mon Sep 17 00:00:00 2001 From: Philippe Teuwen Date: Sun, 24 Sep 2023 18:56:28 +0200 Subject: [PATCH 28/38] Fix shadow mode not being preserved. Breaking change: nfc_tag_mf1_write_mode_t has one new mode SHADOW_REQ. Client can set SHADOW or SHADOW_REQ, internally it will be transformed to SHADOW_REQ. When it's time to save data, if SHADOW_REQ, the fw will change it to SHADOW and still allow to save the data before acting as shadow. Breaking: when client reads back MF1 write mode just after settings SHADOW and before saving config, it will receive SHADOW_REQ and must be ready to handle this new enum value. --- CHANGELOG.md | 1 + firmware/application/src/rfid/nfctag/hf/nfc_mf1.c | 7 +++++++ firmware/application/src/rfid/nfctag/hf/nfc_mf1.h | 9 +++++---- software/script/chameleon_cmd.py | 4 ++++ 4 files changed, 17 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c378812..d415325b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ All notable changes to this project will be documented in this file. This project uses the changelog in accordance with [keepchangelog](http://keepachangelog.com/). Please use this to write notable changes, which is not the same as git commit log... ## [unreleased][unreleased] + - Fixed MF1 write mode SHADOW was not preserved properly (@doegox) - Changed slot enabled logic: now we have separate enabled_hf and enabled_lf, changed GET_ENABLED_SLOTS and SET_SLOT_ENABLE (@doegox) - Changed tag type enum to be ready for new types, changed stored slotConfig and GET_SLOT_INFO (@doegox) - Removed MF1_DETECT_DARKSIDE (@doegox) diff --git a/firmware/application/src/rfid/nfctag/hf/nfc_mf1.c b/firmware/application/src/rfid/nfctag/hf/nfc_mf1.c index 14c4ce5a..417c6753 100644 --- a/firmware/application/src/rfid/nfctag/hf/nfc_mf1.c +++ b/firmware/application/src/rfid/nfctag/hf/nfc_mf1.c @@ -1119,6 +1119,10 @@ int nfc_tag_mf1_data_savecb(tag_specific_type_t type, tag_data_buffer_t *buffer) NRF_LOG_INFO("The mf1 is shadow write mode."); return 0; } + if (m_tag_information->config.mode_block_write == NFC_TAG_MF1_WRITE_SHADOW_REQ) { + NRF_LOG_INFO("The mf1 will be set to shadow write mode."); + m_tag_information->config.mode_block_write = NFC_TAG_MF1_WRITE_SHADOW; + } // Save the corresponding size data according to the current label type return get_information_size_by_tag_type(type, false); } else { @@ -1259,6 +1263,9 @@ bool nfc_tag_mf1_is_use_mf1_coll_res(void) { // Set write mode void nfc_tag_mf1_set_write_mode(nfc_tag_mf1_write_mode_t write_mode) { + if (write_mode == NFC_TAG_MF1_WRITE_SHADOW) { + write_mode = NFC_TAG_MF1_WRITE_SHADOW_REQ; + } m_tag_information->config.mode_block_write = write_mode; } diff --git a/firmware/application/src/rfid/nfctag/hf/nfc_mf1.h b/firmware/application/src/rfid/nfctag/hf/nfc_mf1.h index d7ac34e6..35217149 100644 --- a/firmware/application/src/rfid/nfctag/hf/nfc_mf1.h +++ b/firmware/application/src/rfid/nfctag/hf/nfc_mf1.h @@ -15,10 +15,11 @@ //MF1 label writing mode typedef enum { - NFC_TAG_MF1_WRITE_NORMAL = 0u, - NFC_TAG_MF1_WRITE_DENIED = 1u, - NFC_TAG_MF1_WRITE_DECEIVE = 2u, - NFC_TAG_MF1_WRITE_SHADOW = 3u, + NFC_TAG_MF1_WRITE_NORMAL = 0u, + NFC_TAG_MF1_WRITE_DENIED = 1u, + NFC_TAG_MF1_WRITE_DECEIVE = 2u, + NFC_TAG_MF1_WRITE_SHADOW = 3u, + NFC_TAG_MF1_WRITE_SHADOW_REQ = 4u, } nfc_tag_mf1_write_mode_t; // MF1 tag Gen1a mode state machine diff --git a/software/script/chameleon_cmd.py b/software/script/chameleon_cmd.py index 5417e42b..3da9ddc6 100644 --- a/software/script/chameleon_cmd.py +++ b/software/script/chameleon_cmd.py @@ -265,6 +265,8 @@ class MifareClassicWriteMode(enum.IntEnum): DECEIVE = 2 # Store data to RAM, but not to ROM SHADOW = 3 + # Shadow requested, will be changed to SHADOW and stored to ROM + SHADOW_REQ = 4 @staticmethod def list(): @@ -279,6 +281,8 @@ def __str__(self): return "Deceive" elif self == MifareClassicWriteMode.SHADOW: return "Shadow" + elif self == MifareClassicWriteMode.SHADOW_REQ: + return "Shadow requested" return "None" From 313d772a9998c6a605516098dc97cd50fb99bf79 Mon Sep 17 00:00:00 2001 From: Philippe Teuwen Date: Sun, 24 Sep 2023 22:09:00 +0200 Subject: [PATCH 29/38] hf14a_raw: use @expect_response and fix tests --- software/script/chameleon_cli_unit.py | 19 ++++---- software/script/chameleon_cmd.py | 67 ++++++++++++++------------- software/script/chameleon_com.py | 11 +++-- 3 files changed, 51 insertions(+), 46 deletions(-) diff --git a/software/script/chameleon_cli_unit.py b/software/script/chameleon_cli_unit.py index 63bc65dd..fa21f972 100644 --- a/software/script/chameleon_cli_unit.py +++ b/software/script/chameleon_cli_unit.py @@ -1638,15 +1638,12 @@ def on_exec(self, args: argparse.Namespace): # Exec 14a raw cmd. resp = self.cmd.hf14a_raw(options, args.timeout, data_bytes, args.bits) - if resp.status == chameleon_status.Device.HF_TAG_OK: - if len(resp.data) > 0: - print( - # print head - " - " + - # print data - ' '.join([hex(byte).replace('0x', '').rjust(2, '0') for byte in resp.data]) - ) - else: - print(F" [*] {CY}No response{C0}") + if len(resp) > 0: + print( + # print head + " - " + + # print data + ' '.join([hex(byte).replace('0x', '').rjust(2, '0') for byte in resp]) + ) else: - print(f" [!] {CR}{chameleon_status.message[resp.status]}{C0} ") + print(F" [*] {CY}No response{C0}") diff --git a/software/script/chameleon_cmd.py b/software/script/chameleon_cmd.py index 3da9ddc6..acbb1df7 100644 --- a/software/script/chameleon_cmd.py +++ b/software/script/chameleon_cmd.py @@ -618,6 +618,7 @@ def mf1_write_one_block(self, block, type_value, key, block_data): resp.data = resp.status == chameleon_status.Device.HF_TAG_OK return resp + @expect_response(chameleon_status.Device.HF_TAG_OK) def hf14a_raw(self, options, resp_timeout_ms=100, data=[], bitlen=None): """ Send raw cmd to 14a tag @@ -1213,7 +1214,11 @@ def set_ble_pairing_enable(self, enabled: bool): def test_fn(): # connect to chameleon dev = chameleon_com.ChameleonCom() - dev.open("com19") + try: + dev.open('com19') + except chameleon_com.OpenFailException: + dev.open('/dev/ttyACM0') + cml = ChameleonCMD(dev) ver = cml.get_app_version() print(f"Firmware number of application: {ver[0]}.{ver[1]}") @@ -1232,44 +1237,42 @@ def test_fn(): 'check_response_crc': 0, } - # unlock 1 - resp = cml.hf14a_raw(options=options, resp_timeout_ms=1000, data=[0x40], bitlen=7) - - if resp.status == 0x00 and resp.data[0] == 0x0a: - print("Gen1A unlock 1 success") - # unlock 2 - resp = cml.hf14a_raw(options=options, resp_timeout_ms=1000, data=[0x43]) - if resp.status == 0x00 and resp.data[0] == 0x0a: - print("Gen1A unlock 2 success") - print("Start dump gen1a memory...") - block = 0 - while block < 64: - # Tag read block cmd - cmd_read_gen1a_block = [0x30, block] - + try: + # unlock 1 + resp = cml.hf14a_raw(options=options, resp_timeout_ms=1000, data=[0x40], bitlen=7) + + if resp[0] == 0x0a: + print("Gen1A unlock 1 success") + # unlock 2 + resp = cml.hf14a_raw(options=options, resp_timeout_ms=1000, data=[0x43]) + if resp[0] == 0x0a: + print("Gen1A unlock 2 success") + print("Start dump gen1a memory...") # Transfer with crc options['append_crc'] = 1 options['check_response_crc'] = 1 - resp = cml.hf14a_raw(options=options, resp_timeout_ms=100, data=cmd_read_gen1a_block) - - print(f"Block {block} : {resp.data.hex()}") - block += 1 - - # Close rf field - options['keep_rf_field'] = 0 - resp = cml.hf14a_raw(options=options) + block = 0 + while block < 64: + # Tag read block cmd + cmd_read_gen1a_block = [0x30, block] + if block == 63: + options['keep_rf_field'] = 0 + resp = cml.hf14a_raw(options=options, resp_timeout_ms=100, data=cmd_read_gen1a_block) + + print(f"Block {block} : {resp.hex()}") + block += 1 + + else: + print("Gen1A unlock 2 fail") else: - print("Gen1A unlock 2 fail") - else: - print("Gen1A unlock 1 fail") + print("Gen1A unlock 1 fail") + except: + options['keep_rf_field'] = 0 + options['wait_response'] = 0 + cml.hf14a_raw(options=options) # disconnect dev.close() - # never exit - while True: - pass - - if __name__ == '__main__': test_fn() diff --git a/software/script/chameleon_com.py b/software/script/chameleon_com.py index 023f17e2..eeee8d0b 100644 --- a/software/script/chameleon_com.py +++ b/software/script/chameleon_com.py @@ -341,6 +341,11 @@ def send_cmd_sync(self, cmd: int, data: bytearray or bytes or list or int = None if __name__ == '__main__': - cml = ChameleonCom().open("com19") - resp = cml.send_cmd_sync(0x03E9, bytearray([0x01, 0x02]), 0xBEEF) - print(resp) + try: + cml = ChameleonCom().open('com19') + except OpenFailException: + cml = ChameleonCom().open('/dev/ttyACM0') + resp = cml.send_cmd_sync(0x03E8, None, 0) + print(resp.status) + print(resp.data) + cml.close() From 790399350477875b75004de4eff52be8a7f1421b Mon Sep 17 00:00:00 2001 From: Philippe Teuwen Date: Sun, 24 Sep 2023 22:09:34 +0200 Subject: [PATCH 30/38] Adding MIFARE Ultralight reading, wip --- CHANGELOG.md | 1 + software/script/chameleon_cli_unit.py | 105 ++++++++++++++++++++++++++ 2 files changed, 106 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d415325b..1f32d1af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ All notable changes to this project will be documented in this file. This project uses the changelog in accordance with [keepchangelog](http://keepachangelog.com/). Please use this to write notable changes, which is not the same as git commit log... ## [unreleased][unreleased] + - Added MIFARE Ultralight reading features (@FlUxIuS & @doegox) - Fixed MF1 write mode SHADOW was not preserved properly (@doegox) - Changed slot enabled logic: now we have separate enabled_hf and enabled_lf, changed GET_ENABLED_SLOTS and SET_SLOT_ENABLE (@doegox) - Changed tag type enum to be ready for new types, changed stored slotConfig and GET_SLOT_INFO (@doegox) diff --git a/software/script/chameleon_cli_unit.py b/software/script/chameleon_cli_unit.py index fa21f972..d6dbcfbd 100644 --- a/software/script/chameleon_cli_unit.py +++ b/software/script/chameleon_cli_unit.py @@ -9,6 +9,7 @@ import time import serial.tools.list_ports import threading +import struct from platform import uname import chameleon_com @@ -190,6 +191,7 @@ def on_exec(self, args: argparse.Namespace): hf_14a = hf.subgroup('14a', 'ISO14443-a tag read/write/info...') hf_mf = hf.subgroup('mf', 'Mifare Classic mini/1/2/4, attack/read/write') hf_mf_detection = hf.subgroup('detection', 'Mifare Classic detection log') +hf_mfu = hf.subgroup('mfu', 'Mifare Ultralight, read/write') lf = CLITree('lf', 'low frequency tag/reader') lf_em = lf.subgroup('em', 'EM410x read/write/emulator') @@ -592,6 +594,24 @@ def on_exec(self, args: argparse.Namespace): raise NotImplementedError("Please implement this") +class BaseMFUAuthOpera(ReaderRequiredUnit): + def args_parser(self) -> ArgumentParserNoExit or None: + parser = ArgumentParserNoExit() + # TODO: + # -k, --key Authentication key (UL-C 16 bytes, EV1/NTAG 4 bytes) + # -l Swap entered key's endianness + return parser + + def get_param(self, args): + class Param: + def __init__(self): + pass + return Param() + + def on_exec(self, args: argparse.Namespace): + raise NotImplementedError("Please implement this") + + @hf_mf.command('rdbl', 'Mifare Classic read one block') class HFMFRDBL(BaseMF1AuthOpera): # hf mf rdbl -b 2 -t A -k FFFFFFFFFFFF @@ -955,6 +975,91 @@ def scan(self): def on_exec(self, args: argparse.Namespace): return self.scan() +@hf_mfu.command('rdpg', 'MIFARE Ultralight read one page') +class HFMFURDPG(BaseMFUAuthOpera): + # hf mfu rdpg -p 2 + + def args_parser(self) -> ArgumentParserNoExit or None: + parser = super(HFMFURDPG, self).args_parser() + parser.add_argument('-p', '--page', type=int, required=True, metavar="decimal", + help="The page where the key will be used against") + return parser + + def get_param(self, args): + class Param: + def __init__(self): + self.page = args.page + return Param() + + def on_exec(self, args: argparse.Namespace): + param = self.get_param(args) + + options = { + 'activate_rf_field': 0, + 'wait_response': 1, + 'append_crc': 1, + 'auto_select': 1, + 'keep_rf_field': 0, + 'check_response_crc': 1, + } + # TODO: auth first if a key is given + resp = self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!BB', 0x30, param.page)) + print(f" - Data: {resp[:4].hex()}") + +@hf_mfu.command('dump', 'MIFARE Ultralight dump pages') +class HFMFUDUMP(BaseMFUAuthOpera): + # hf mfu dump [-p start_page] [-q number_pages] [-f output_file] + def args_parser(self) -> ArgumentParserNoExit or None: + parser = super(HFMFUDUMP, self).args_parser() + parser.add_argument('-p', '--page', type=int, required=False, metavar="decimal", default=0, + help="Manually set number of pages to dump") + parser.add_argument('-q', '--qty', type=int, required=False, metavar="decimal", default=16, + help="Manually set number of pages to dump") + parser.add_argument('-f', '--file', type=str, required=False, default="", + help="Specify a filename for dump file") + return parser + + def get_param(self, args): + class Param: + def __init__(self): + self.start_page = args.page + self.stop_page = args.page + args.qty + self.output_file = args.file + return Param() + + def on_exec(self, args: argparse.Namespace): + param = self.get_param(args) + fd = None + save_as_eml = False + if param.output_file != "": + if param.output_file.endswith('.eml'): + fd = open(param.output_file, 'w+') + save_as_eml = True + else: + fd = open(param.output_file, 'wb+') + # TODO: auth first if a key is given + options = { + 'activate_rf_field': 0, + 'wait_response': 1, + 'append_crc': 1, + 'auto_select': 1, + 'keep_rf_field': 0, + 'check_response_crc': 1, + } + for i in range(param.start_page, param.stop_page): + resp = self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!BB', 0x30, i)) + # TODO: can be optimized as we get 4 pages at once but beware of wrapping in case of end of memory or LOCK on ULC and no key provided + data = resp[:4] + print(f" - Page {i:2}: {data.hex()}") + if fd is not None: + if save_as_eml: + fd.write(data.hex()+'\n') + else: + fd.write(data) + if fd is not None: + print(f" - {colorama.Fore.GREEN}Dump written in {param.output_file}.{colorama.Style.RESET_ALL}") + fd.close() + @lf_em.command('read', 'Scan em410x tag and print id') class LFEMRead(ReaderRequiredUnit): From f5b50aa3124c4b918130891220b71479407d218c Mon Sep 17 00:00:00 2001 From: Thomas Burnham <40033412+aramova@users.noreply.github.com> Date: Fri, 15 Sep 2023 15:32:09 -0400 Subject: [PATCH 31/38] Increase initial button wakeup from 4 to 8 seconds Initial button press wakeup of 4 seconds is sometimes inadequate to establish BT connection. This change will expand the Initial button wakeup from 4 seconds to 8 seconds. Device sleep behavior after will be as follows: (Changed) Initial Button Wakeup to Sleep : 8s (No Change) Subsequent Button Action to Sleep : 4s Leaving RF Field wake to sleep : 3s BLE Disconnect to sleep : 4s USB Power Disconnect to sleep : 3s Example behavior : User wakes device with Button B (8 Sec to sleep) User pushes button B again (Now 4 Sec to sleep) User Places device in RF field (Maintain wake) User removes device from RF field (3 sec to sleep) If user discontinues the user journey at any point, the identified condition will invoke sleep at defined times. --- firmware/application/src/utils/syssleep.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firmware/application/src/utils/syssleep.h b/firmware/application/src/utils/syssleep.h index af3349b5..4e1e99c1 100644 --- a/firmware/application/src/utils/syssleep.h +++ b/firmware/application/src/utils/syssleep.h @@ -4,7 +4,7 @@ #include // Wake up equipment -#define SLEEP_DELAY_MS_BUTTON_WAKEUP 4000 // The sleep delay of the button awakened +#define SLEEP_DELAY_MS_BUTTON_WAKEUP 8000 // The sleep delay of the button awakened #define SLEEP_DELAY_MS_FIELD_WAKEUP 4000 // The sleep delay (including high and low frequencies) of the field wake -up (including high and low frequency) #define SLEEP_DELAY_MS_FIRST_POWER 1000 // The sleep delay of the first power supply (access to the battery) From e23ad695e6703f673469190861e6b8f12076a8ff Mon Sep 17 00:00:00 2001 From: Philippe Teuwen Date: Sun, 24 Sep 2023 23:21:59 +0200 Subject: [PATCH 32/38] colors --- firmware/application/src/app_cmd.c | 4 +-- firmware/application/src/app_main.c | 2 +- .../application/src/rfid/nfctag/hf/nfc_14a.c | 4 +-- .../src/rfid/nfctag/lf/lf_tag_em.c | 2 +- .../src/rfid/reader/hf/mf1_toolbox.c | 2 +- firmware/application/src/rgb_marquee.c | 11 +++++--- firmware/common/hw_connect.c | 26 +++++++++++++++---- firmware/common/hw_connect.h | 12 ++++++++- 8 files changed, 47 insertions(+), 16 deletions(-) diff --git a/firmware/application/src/app_cmd.c b/firmware/application/src/app_cmd.c index 42b0b67b..745b6b32 100644 --- a/firmware/application/src/app_cmd.c +++ b/firmware/application/src/app_cmd.c @@ -28,7 +28,7 @@ static void change_slot_auto(uint8_t slot) { device_mode_t mode = get_device_mode(); tag_emulation_change_slot(slot, mode != DEVICE_MODE_READER); light_up_by_slot(); - set_slot_light_color(0); + set_slot_light_color(RGB_RED); } @@ -557,7 +557,7 @@ static data_frame_tx_t *cmd_processor_set_slot_enable(uint16_t cmd, uint16_t sta uint8_t slot_prev = tag_emulation_slot_find_next(slot_now); NRF_LOG_INFO("slot_now = %d, slot_prev = %d", slot_now, slot_prev); if (slot_prev == slot_now) { - set_slot_light_color(3); + set_slot_light_color(RGB_MAGENTA); } else { change_slot_auto(slot_prev); } diff --git a/firmware/application/src/app_main.c b/firmware/application/src/app_main.c index 7380759c..a6eeab14 100644 --- a/firmware/application/src/app_main.c +++ b/firmware/application/src/app_main.c @@ -250,7 +250,7 @@ static void system_off_enter(void) { if (g_is_low_battery_shutdown) { // Don't create too complex animations, just blink LED1 three times. rgb_marquee_stop(); - set_slot_light_color(0); + set_slot_light_color(RGB_RED); for (uint8_t i = 0; i <= 3; i++) { nrf_gpio_pin_set(LED_1); bsp_delay_ms(100); diff --git a/firmware/application/src/rfid/nfctag/hf/nfc_14a.c b/firmware/application/src/rfid/nfctag/hf/nfc_14a.c index 40cc6439..6e01c9aa 100644 --- a/firmware/application/src/rfid/nfctag/hf/nfc_14a.c +++ b/firmware/application/src/rfid/nfctag/hf/nfc_14a.c @@ -617,7 +617,7 @@ void nfc_tag_14a_event_callback(nrfx_nfct_evt_t const *p_event) { g_is_tag_emulating = true; g_usb_led_marquee_enable = false; - set_slot_light_color(1); + set_slot_light_color(RGB_GREEN); TAG_FIELD_LED_ON() NRF_LOG_INFO("HF FIELD DETECTED"); @@ -661,7 +661,7 @@ void nfc_tag_14a_event_callback(nrfx_nfct_evt_t const *p_event) { break; } case NRFX_NFCT_EVT_RX_FRAMEEND: { - set_slot_light_color(1); + set_slot_light_color(RGB_GREEN); TAG_FIELD_LED_ON() // NRF_LOG_INFO("RX FRAMEEND.\n"); diff --git a/firmware/application/src/rfid/nfctag/lf/lf_tag_em.c b/firmware/application/src/rfid/nfctag/lf/lf_tag_em.c index ef90e7dd..9d480557 100644 --- a/firmware/application/src/rfid/nfctag/lf/lf_tag_em.c +++ b/firmware/application/src/rfid/nfctag/lf/lf_tag_em.c @@ -299,7 +299,7 @@ static void lpcomp_event_handler(nrf_lpcomp_event_t event) { g_usb_led_marquee_enable = false; // LED status update - set_slot_light_color(2); + set_slot_light_color(RGB_BLUE); TAG_FIELD_LED_ON() //In any case, every time the state finds changes, you need to reset the BIT location of the sending diff --git a/firmware/application/src/rfid/reader/hf/mf1_toolbox.c b/firmware/application/src/rfid/reader/hf/mf1_toolbox.c index e8906345..98858b8f 100644 --- a/firmware/application/src/rfid/reader/hf/mf1_toolbox.c +++ b/firmware/application/src/rfid/reader/hf/mf1_toolbox.c @@ -382,7 +382,7 @@ uint8_t darkside_recover_key(uint8_t targetBlk, uint8_t targetTyp, // Verification instructions need to add CRC16 crc_14a_append(tag_auth, 2); rgb_marquee_stop(); - set_slot_light_color(1); + set_slot_light_color(RGB_GREEN); uint32_t *led_pins = hw_get_led_array(); for (uint8_t i = 0; i < RGB_LIST_NUM; i++) { nrf_gpio_pin_clear(led_pins[i]); diff --git a/firmware/application/src/rgb_marquee.c b/firmware/application/src/rgb_marquee.c index f4aece9a..3a5d259f 100644 --- a/firmware/application/src/rgb_marquee.c +++ b/firmware/application/src/rgb_marquee.c @@ -33,6 +33,7 @@ nrf_drv_pwm_config_t pwm_config = {//PWM configuration structure }; static autotimer *timer; static uint8_t ledblink6_step = 0; +static uint8_t ledblink6_color = RGB_RED; static uint8_t ledblink1_step = 0; extern bool g_usb_led_marquee_enable; @@ -392,7 +393,7 @@ void ledblink6(void) { } if (ledblink6_step == 0) { - set_slot_light_color(0); + set_slot_light_color(ledblink6_color); for (uint8_t i = 0; i < RGB_LIST_NUM; i++) { nrf_gpio_pin_clear(led_array[i]); } @@ -420,7 +421,7 @@ void ledblink6(void) { pwm_sequ_val.channel_2 = pwm_sequ_val.channel_0; pwm_sequ_val.channel_3 = pwm_sequ_val.channel_0; nrfx_pwm_uninit(&pwm0_ins); //Close PWM output - set_slot_light_color(1); + set_slot_light_color(ledblink6_color); nrf_drv_pwm_init(&pwm0_ins, &pwm_config, ledblink6_pwm_callback); nrf_drv_pwm_simple_playback(&pwm0_ins, &seq, 1, NRF_DRV_PWM_FLAG_LOOP); ledblink6_step = 3; @@ -457,7 +458,7 @@ void ledblink6(void) { pwm_sequ_val.channel_2 = pwm_sequ_val.channel_0; pwm_sequ_val.channel_3 = pwm_sequ_val.channel_0; nrfx_pwm_uninit(&pwm0_ins); //Close PWM output - set_slot_light_color(1); + set_slot_light_color(ledblink6_color); nrf_drv_pwm_init(&pwm0_ins, &pwm_config, ledblink6_pwm_callback); nrf_drv_pwm_simple_playback(&pwm0_ins, &seq, 1, NRF_DRV_PWM_FLAG_LOOP); ledblink6_step = 7; @@ -477,6 +478,10 @@ void ledblink6(void) { } } else { ledblink6_step = 0; + //if (++ledblink6_color == RGB_WHITE) ledblink6_color = RGB_RED; + uint8_t new_color=rand()%6; + for (; new_color == ledblink6_color; new_color=rand()%6); + ledblink6_color = new_color; } } } diff --git a/firmware/common/hw_connect.c b/firmware/common/hw_connect.c index 7d2d59e4..a4249c51 100644 --- a/firmware/common/hw_connect.c +++ b/firmware/common/hw_connect.c @@ -238,20 +238,36 @@ void init_leds(void) { * @brief Function for enter tag emulation mode * @param color: 0 means r, 1 means g, 2 means b */ -void set_slot_light_color(uint8_t color) { +void set_slot_light_color(chameleon_rgb_type_t color) { nrf_gpio_pin_set(LED_R); nrf_gpio_pin_set(LED_G); nrf_gpio_pin_set(LED_B); switch (color) { - case 0: + case RGB_RED: nrf_gpio_pin_clear(LED_R); break; - case 1: + case RGB_GREEN: nrf_gpio_pin_clear(LED_G); break; - case 2: + case RGB_BLUE: + nrf_gpio_pin_clear(LED_B); + break; + case RGB_MAGENTA: + nrf_gpio_pin_clear(LED_B); + nrf_gpio_pin_clear(LED_R); + break; + case RGB_YELLOW: + nrf_gpio_pin_clear(LED_R); + nrf_gpio_pin_clear(LED_G); + break; + case RGB_CYAN: + nrf_gpio_pin_clear(LED_G); + nrf_gpio_pin_clear(LED_B); + break; + case RGB_WHITE: + nrf_gpio_pin_clear(LED_R); + nrf_gpio_pin_clear(LED_G); nrf_gpio_pin_clear(LED_B); break; } - } diff --git a/firmware/common/hw_connect.h b/firmware/common/hw_connect.h index 92aa64b5..8e1b6b6f 100644 --- a/firmware/common/hw_connect.h +++ b/firmware/common/hw_connect.h @@ -13,6 +13,16 @@ typedef enum { CHAMELEON_LITE, } chameleon_device_type_t; +typedef enum { + RGB_RED, + RGB_GREEN, + RGB_BLUE, + RGB_MAGENTA, + RGB_YELLOW, + RGB_CYAN, + RGB_WHITE +} chameleon_rgb_type_t; + #define MAX_LED_NUM 8 #define MAX_RGB_NUM 3 @@ -97,7 +107,7 @@ uint32_t *hw_get_led_reversal_array(void); uint32_t *hw_get_rgb_array(void); chameleon_device_type_t hw_get_device_type(void); uint8_t hw_get_version_code(void); -void set_slot_light_color(uint8_t color); +void set_slot_light_color(chameleon_rgb_type_t color); #endif From 372cfead0c5e81b13ca311cb249a2ec1fa3d9460 Mon Sep 17 00:00:00 2001 From: Philippe Teuwen Date: Mon, 25 Sep 2023 00:51:43 +0200 Subject: [PATCH 33/38] style --- firmware/application/src/app_cmd.c | 32 +++++----- .../src/rfid/nfctag/tag_base_type.h | 4 +- .../src/rfid/nfctag/tag_emulation.c | 30 +++++----- .../application/src/rfid/reader/hf/rc522.c | 60 +++++++++---------- .../application/src/rfid/reader/hf/rc522.h | 4 +- firmware/application/src/rgb_marquee.c | 4 +- software/script/chameleon_cli_unit.py | 29 +++++---- software/script/chameleon_cmd.py | 27 +++++---- 8 files changed, 99 insertions(+), 91 deletions(-) diff --git a/firmware/application/src/app_cmd.c b/firmware/application/src/app_cmd.c index 745b6b32..5332c09c 100644 --- a/firmware/application/src/app_cmd.c +++ b/firmware/application/src/app_cmd.c @@ -392,7 +392,7 @@ static data_frame_tx_t *cmd_processor_mf1_write_one_block(uint16_t cmd, uint16_t static data_frame_tx_t *cmd_processor_hf14a_raw(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { // Response Buffer - uint8_t resp[DEF_FIFO_LENGTH] = { 0x00 }; + uint8_t resp[DEF_FIFO_LENGTH] = { 0x00 }; uint16_t resp_length = 0; typedef struct { @@ -427,22 +427,22 @@ static data_frame_tx_t *cmd_processor_hf14a_raw(uint16_t cmd, uint16_t status, u NRF_LOG_INFO("reserved = %d", payload->options.reserved); status = pcd_14a_reader_raw_cmd( - payload->options.activate_rf_field, - payload->options.wait_response, - payload->options.append_crc, - payload->options.auto_select, - payload->options.keep_rf_field, - payload->options.check_response_crc, + payload->options.activate_rf_field, + payload->options.wait_response, + payload->options.append_crc, + payload->options.auto_select, + payload->options.keep_rf_field, + payload->options.check_response_crc, - U16NTOHS(payload->resp_timeout), + U16NTOHS(payload->resp_timeout), - U16NTOHS(payload->data_bitlength), - payload->data_buffer, + U16NTOHS(payload->data_bitlength), + payload->data_buffer, - resp, - &resp_length, - U8ARR_BIT_LEN(resp) - ); + resp, + &resp_length, + U8ARR_BIT_LEN(resp) + ); return data_frame_make(cmd, status, resp_length, resp); } @@ -552,7 +552,7 @@ static data_frame_tx_t *cmd_processor_set_slot_enable(uint16_t cmd, uint16_t sta uint8_t slot_now = payload->slot_index; tag_emulation_slot_set_enable(slot_now, payload->sense_type, payload->enabled); if ((!payload->enabled) && - (!tag_emulation_slot_is_enabled(slot_now, payload->sense_type == TAG_SENSE_HF ? TAG_SENSE_LF : TAG_SENSE_HF))) { + (!tag_emulation_slot_is_enabled(slot_now, payload->sense_type == TAG_SENSE_HF ? TAG_SENSE_LF : TAG_SENSE_HF))) { // HF and LF disabled, need to change slot uint8_t slot_prev = tag_emulation_slot_find_next(slot_now); NRF_LOG_INFO("slot_now = %d, slot_prev = %d", slot_now, slot_prev); @@ -863,7 +863,7 @@ static data_frame_tx_t *cmd_processor_get_enabled_slots(uint16_t cmd, uint16_t s payload[slot].enabled_hf = tag_emulation_slot_is_enabled(slot, TAG_SENSE_HF); payload[slot].enabled_lf = tag_emulation_slot_is_enabled(slot, TAG_SENSE_LF); } - return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, sizeof(payload), (uint8_t*)&payload); + return data_frame_make(cmd, STATUS_DEVICE_SUCCESS, sizeof(payload), (uint8_t *)&payload); } static data_frame_tx_t *cmd_processor_get_ble_connect_key(uint16_t cmd, uint16_t status, uint16_t length, uint8_t *data) { diff --git a/firmware/application/src/rfid/nfctag/tag_base_type.h b/firmware/application/src/rfid/nfctag/tag_base_type.h index feb9473b..9f6b2027 100644 --- a/firmware/application/src/rfid/nfctag/tag_base_type.h +++ b/firmware/application/src/rfid/nfctag/tag_base_type.h @@ -109,8 +109,8 @@ typedef enum { TAG_TYPE_NTAG_216 typedef struct { - tag_specific_type_t tag_hf; - tag_specific_type_t tag_lf; + tag_specific_type_t tag_hf; + tag_specific_type_t tag_lf; } tag_slot_specific_type_t; diff --git a/firmware/application/src/rfid/nfctag/tag_emulation.c b/firmware/application/src/rfid/nfctag/tag_emulation.c index 9819d67f..a7ebdabb 100644 --- a/firmware/application/src/rfid/nfctag/tag_emulation.c +++ b/firmware/application/src/rfid/nfctag/tag_emulation.c @@ -39,10 +39,10 @@ static tag_specific_type_t tag_specific_type_hf_values[] = { TAG_SPECIFIC_TYPE_H bool is_tag_specific_type_valid(tag_specific_type_t tag_type) { bool valid = false; - for (uint16_t i=0; i < ARRAYLEN(tag_specific_type_lf_values); i++) { + for (uint16_t i = 0; i < ARRAYLEN(tag_specific_type_lf_values); i++) { valid |= (tag_type == tag_specific_type_lf_values[i]); } - for (uint16_t i=0; i < ARRAYLEN(tag_specific_type_hf_values); i++) { + for (uint16_t i = 0; i < ARRAYLEN(tag_specific_type_hf_values); i++) { valid |= (tag_type == tag_specific_type_hf_values[i]); } return valid; @@ -398,7 +398,7 @@ void tag_emulation_sense_switch(tag_sense_type_t type, bool enable) { break; case TAG_SENSE_HF: if (enable && (slotConfig.slots[slot].enabled_hf) && - (slotConfig.slots[slot].tag_hf != TAG_TYPE_UNDEFINED)) { + (slotConfig.slots[slot].tag_hf != TAG_TYPE_UNDEFINED)) { nfc_tag_14a_sense_switch(true); } else { nfc_tag_14a_sense_switch(false); @@ -406,7 +406,7 @@ void tag_emulation_sense_switch(tag_sense_type_t type, bool enable) { break; case TAG_SENSE_LF: if (enable && (slotConfig.slots[slot].enabled_lf) && - (slotConfig.slots[slot].tag_lf != TAG_TYPE_UNDEFINED)) { + (slotConfig.slots[slot].tag_lf != TAG_TYPE_UNDEFINED)) { lf_tag_125khz_sense_switch(true); } else { lf_tag_125khz_sense_switch(false); @@ -425,32 +425,32 @@ static void tag_emulation_migrate_slot_config_v0_to_v8(void) { // Populate new slotConfig struct slotConfig.version = TAG_SLOT_CONFIG_CURRENT_VERSION; slotConfig.active_slot = tmpbuf[0]; - for (uint8_t i = 0; i< ARRAYLEN(slotConfig.slots); i++) { - bool enabled = tmpbuf[4+(i*4)] & 1; + for (uint8_t i = 0; i < ARRAYLEN(slotConfig.slots); i++) { + bool enabled = tmpbuf[4 + (i * 4)] & 1; - slotConfig.slots[i].tag_hf = tmpbuf[4+(i*4)+2]; - for (uint8_t j=0; j < ARRAYLEN(tag_specific_type_old2new_hf_values); j++) { + slotConfig.slots[i].tag_hf = tmpbuf[4 + (i * 4) + 2]; + for (uint8_t j = 0; j < ARRAYLEN(tag_specific_type_old2new_hf_values); j++) { if (slotConfig.slots[i].tag_hf == tag_specific_type_old2new_hf_values[j][0]) { slotConfig.slots[i].tag_hf = tag_specific_type_old2new_hf_values[j][1]; } } slotConfig.slots[i].enabled_hf = slotConfig.slots[i].tag_hf != TAG_TYPE_UNDEFINED ? enabled : false; - NRF_LOG_INFO("Slot %i HF: %02X->%04X enabled:%i", i, tmpbuf[4+(i*4)+2], slotConfig.slots[i].tag_hf, slotConfig.slots[i].enabled_hf); + NRF_LOG_INFO("Slot %i HF: %02X->%04X enabled:%i", i, tmpbuf[4 + (i * 4) + 2], slotConfig.slots[i].tag_hf, slotConfig.slots[i].enabled_hf); - slotConfig.slots[i].tag_lf = tmpbuf[4+(i*4)+3]; - for (uint8_t j=0; j < ARRAYLEN(tag_specific_type_old2new_lf_values); j++) { + slotConfig.slots[i].tag_lf = tmpbuf[4 + (i * 4) + 3]; + for (uint8_t j = 0; j < ARRAYLEN(tag_specific_type_old2new_lf_values); j++) { if (slotConfig.slots[i].tag_lf == tag_specific_type_old2new_lf_values[j][0]) { slotConfig.slots[i].tag_lf = tag_specific_type_old2new_lf_values[j][1]; } } slotConfig.slots[i].enabled_lf = slotConfig.slots[i].tag_lf != TAG_TYPE_UNDEFINED ? enabled : false; - NRF_LOG_INFO("Slot %i LF: %02X->%04X enabled:%i", i, tmpbuf[4+(i*4)+3], slotConfig.slots[i].tag_lf, slotConfig.slots[i].enabled_lf); + NRF_LOG_INFO("Slot %i LF: %02X->%04X enabled:%i", i, tmpbuf[4 + (i * 4) + 3], slotConfig.slots[i].tag_lf, slotConfig.slots[i].enabled_lf); } } static void tag_emulation_migrate_slot_config(void) { - switch(slotConfig.version) { + switch (slotConfig.version) { case 0: case 1: case 2: @@ -627,7 +627,7 @@ uint8_t tag_emulation_slot_find_next(uint8_t slot_now) { uint8_t start_slot = (slot_now + 1 == TAG_MAX_SLOT_NUM) ? 0 : slot_now + 1; for (uint8_t i = start_slot;;) { if (i == slot_now) return slot_now; // No other activated card slots were found after a loop - if (slotConfig.slots[i].enabled_hf || slotConfig.slots[i].enabled_lf ) return i; // Check whether the card slot that is currently traversed is enabled, so that the capacity determines that the current card slot is the card slot that can effectively enable capacity + if (slotConfig.slots[i].enabled_hf || slotConfig.slots[i].enabled_lf) return i; // Check whether the card slot that is currently traversed is enabled, so that the capacity determines that the current card slot is the card slot that can effectively enable capacity i++; if (i == TAG_MAX_SLOT_NUM) { // Continue the next cycle i = 0; @@ -686,7 +686,7 @@ void tag_emulation_change_type(uint8_t slot, tag_specific_type_t tag_type) { void tag_emulation_factory_init(void) { fds_slot_record_map_t map_info; - // Initialized a dual -frequency card in the card slot, if there is no historical record, it is a new state of factory. + // Initialized a dual -frequency card in the card slot, if there is no historical record, it is a new state of factory. if (slotConfig.slots[0].enabled_hf && slotConfig.slots[0].tag_hf == TAG_TYPE_MIFARE_1024) { // Initialize a high -frequency M1 card in the card slot 1, if it does not exist. get_fds_map_by_slot_sense_type_for_dump(0, TAG_SENSE_HF, &map_info); diff --git a/firmware/application/src/rfid/reader/hf/rc522.c b/firmware/application/src/rfid/reader/hf/rc522.c index 569ba160..087234e5 100644 --- a/firmware/application/src/rfid/reader/hf/rc522.c +++ b/firmware/application/src/rfid/reader/hf/rc522.c @@ -1129,20 +1129,20 @@ inline void pcd_14a_reader_crc_computer(uint8_t use522CalcCRC) { } /** -* @brief : The hf 14a raw command implementation function can be used to send the 14A command with the specified configuration parameters. -* @param :waitResp : Wait for tag response -* @param :appendCrc : Do you want to add CRC before sending -* @param :autoSelect : Automatically select card before sending data -* @param :keepField : Do you want to keep the RF field on after sending -* @param :checkCrc : Is CRC verified after receiving data? If CRC verification is enabled, CRC bytes will be automatically removed after verification is completed. -* @param :waitRespTimeout : If waitResp is enabled, this parameter will be the timeout value to wait for the tag to respond -* @param :szDataSend : The number of bytes or bits of data to be sent -* @param :pDataSend : Pointer to the buffer of the data to be sent +* @brief : The hf 14a raw command implementation function can be used to send the 14A command with the specified configuration parameters. +* @param :waitResp : Wait for tag response +* @param :appendCrc : Do you want to add CRC before sending +* @param :autoSelect : Automatically select card before sending data +* @param :keepField : Do you want to keep the RF field on after sending +* @param :checkCrc : Is CRC verified after receiving data? If CRC verification is enabled, CRC bytes will be automatically removed after verification is completed. +* @param :waitRespTimeout : If waitResp is enabled, this parameter will be the timeout value to wait for the tag to respond +* @param :szDataSend : The number of bytes or bits of data to be sent +* @param :pDataSend : Pointer to the buffer of the data to be sent * -* @retval : Execution Status +* @retval : Execution Status * */ -uint8_t pcd_14a_reader_raw_cmd(bool openRFField, bool waitResp, bool appendCrc, bool autoSelect, bool keepField, bool checkCrc, uint16_t waitRespTimeout, +uint8_t pcd_14a_reader_raw_cmd(bool openRFField, bool waitResp, bool appendCrc, bool autoSelect, bool keepField, bool checkCrc, uint16_t waitRespTimeout, uint16_t szDataSendBits, uint8_t *pDataSend, uint8_t *pDataRecv, uint16_t *pszDataRecv, uint16_t szDataRecvBitMax) { // Status code, default is OK. uint8_t status = HF_TAG_OK; @@ -1203,32 +1203,32 @@ uint8_t pcd_14a_reader_raw_cmd(bool openRFField, bool waitResp, bool appendCrc, } if (szDataSendBits % 8) { status = pcd_14a_reader_bits_transfer( - pDataSend, - szDataSendBits, - NULL, - pDataRecv, - NULL, - pszDataRecv, - szDataRecvBitMax - ); + pDataSend, + szDataSendBits, + NULL, + pDataRecv, + NULL, + pszDataRecv, + szDataRecvBitMax + ); } else { status = pcd_14a_reader_bytes_transfer( - PCD_TRANSCEIVE, - pDataSend, - szDataSendBits / 8, - pDataRecv, - pszDataRecv, - szDataRecvBitMax - ); + PCD_TRANSCEIVE, + pDataSend, + szDataSendBits / 8, + pDataRecv, + pszDataRecv, + szDataRecvBitMax + ); } - + // If we need to receive data, we need to perform further operations on the data based on the remaining configuration after receiving it if (waitResp) { // Number of bits to bytes uint8_t finalRecvBytes = (*pszDataRecv / 8) + (*pszDataRecv % 8 > 0 ? 1 : 0); // If CRC verification is required, we need to perform CRC calculation if (checkCrc) { - if (finalRecvBytes >= 3) { // Ensure at least three bytes (one byte of data+two bytes of CRC) + if (finalRecvBytes >= 3) { // Ensure at least three bytes (one byte of data+two bytes of CRC) // Calculate and store CRC uint8_t crc_buff[DEF_CRC_LENGTH] = { 0x00 }; crc_14a_calculate(pDataRecv, finalRecvBytes - DEF_CRC_LENGTH, crc_buff); @@ -1238,7 +1238,7 @@ uint8_t pcd_14a_reader_raw_cmd(bool openRFField, bool waitResp, bool appendCrc, *pszDataRecv = 0; status = HF_ERR_CRC; } else { - // If the CRC needs to be verified by the device and the device determines that the CRC is normal, + // If the CRC needs to be verified by the device and the device determines that the CRC is normal, // we will return the data without CRC *pszDataRecv = finalRecvBytes - DEF_CRC_LENGTH; } @@ -1263,4 +1263,4 @@ uint8_t pcd_14a_reader_raw_cmd(bool openRFField, bool waitResp, bool appendCrc, } return status; -} \ No newline at end of file +} diff --git a/firmware/application/src/rfid/reader/hf/rc522.h b/firmware/application/src/rfid/reader/hf/rc522.h index 899c1485..e3842d20 100644 --- a/firmware/application/src/rfid/reader/hf/rc522.h +++ b/firmware/application/src/rfid/reader/hf/rc522.h @@ -220,8 +220,8 @@ uint8_t pcd_14a_reader_mf1_read(uint8_t addr, uint8_t *pData); uint8_t pcd_14a_reader_halt_tag(void); void pcd_14a_reader_fast_halt_tag(void); -uint8_t pcd_14a_reader_raw_cmd(bool openRFField, bool waitResp, bool appendCrc, bool autoSelect, bool keepField, bool checkCrc, uint16_t waitRespTimeout, - uint16_t szDataSendBits, uint8_t* pDataSend, uint8_t* pDataRecv, uint16_t* pszDataRecv, uint16_t szDataRecvBitMax); +uint8_t pcd_14a_reader_raw_cmd(bool openRFField, bool waitResp, bool appendCrc, bool autoSelect, bool keepField, bool checkCrc, uint16_t waitRespTimeout, + uint16_t szDataSendBits, uint8_t *pDataSend, uint8_t *pDataRecv, uint16_t *pszDataRecv, uint16_t szDataRecvBitMax); // UID & UFUID tag operation uint8_t pcd_14a_reader_gen1a_unlock(void); diff --git a/firmware/application/src/rgb_marquee.c b/firmware/application/src/rgb_marquee.c index 3a5d259f..42416683 100644 --- a/firmware/application/src/rgb_marquee.c +++ b/firmware/application/src/rgb_marquee.c @@ -479,8 +479,8 @@ void ledblink6(void) { } else { ledblink6_step = 0; //if (++ledblink6_color == RGB_WHITE) ledblink6_color = RGB_RED; - uint8_t new_color=rand()%6; - for (; new_color == ledblink6_color; new_color=rand()%6); + uint8_t new_color = rand() % 6; + for (; new_color == ledblink6_color; new_color = rand() % 6); ledblink6_color = new_color; } } diff --git a/software/script/chameleon_cli_unit.py b/software/script/chameleon_cli_unit.py index d6dbcfbd..5d5aad5c 100644 --- a/software/script/chameleon_cli_unit.py +++ b/software/script/chameleon_cli_unit.py @@ -975,6 +975,7 @@ def scan(self): def on_exec(self, args: argparse.Namespace): return self.scan() + @hf_mfu.command('rdpg', 'MIFARE Ultralight read one page') class HFMFURDPG(BaseMFUAuthOpera): # hf mfu rdpg -p 2 @@ -1006,6 +1007,7 @@ def on_exec(self, args: argparse.Namespace): resp = self.cmd.hf14a_raw(options=options, resp_timeout_ms=200, data=struct.pack('!BB', 0x30, param.page)) print(f" - Data: {resp[:4].hex()}") + @hf_mfu.command('dump', 'MIFARE Ultralight dump pages') class HFMFUDUMP(BaseMFUAuthOpera): # hf mfu dump [-p start_page] [-q number_pages] [-f output_file] @@ -1160,12 +1162,12 @@ def args_parser(self) -> ArgumentParserNoExit or None: def get_slot_name(self, slot, sense): try: name = self.cmd.get_slot_tag_nick(slot, sense).decode(encoding="utf8") - return {'baselen':len(name), 'metalen':len(CC+C0), 'name':f'{CC}{name}{C0}'} + return {'baselen': len(name), 'metalen': len(CC+C0), 'name': f'{CC}{name}{C0}'} except UnexpectedResponseError: - return {'baselen':0, 'metalen':0, 'name':f''} + return {'baselen': 0, 'metalen': 0, 'name': f''} except UnicodeDecodeError: name = "UTF8 Err" - return {'baselen':len(name), 'metalen':len(CC+C0), 'name':f'{CC}{name}{C0}'} + return {'baselen': len(name), 'metalen': len(CC+C0), 'name': f'{CC}{name}{C0}'} # hw slot list def on_exec(self, args: argparse.Namespace): @@ -1180,7 +1182,7 @@ def on_exec(self, args: argparse.Namespace): lfn = self.get_slot_name(slot, chameleon_cmd.TagSenseType.TAG_SENSE_LF) m = max(hfn['baselen'], lfn['baselen']) maxnamelength = m if m > maxnamelength else maxnamelength - slotnames.append({'hf':hfn, 'lf':lfn}) + slotnames.append({'hf': hfn, 'lf': lfn}) for slot in chameleon_cmd.SlotNumber: fwslot = chameleon_cmd.SlotNumber.to_fw(slot) hf_tag_type = chameleon_cmd.TagSpecificType(slotinfo[fwslot]['hf']) @@ -1222,6 +1224,7 @@ def on_exec(self, args: argparse.Namespace): else: print("undef") + @hw_slot.command('change', 'Set emulation tag slot activated.') class HWSlotSet(SlotIndexRequireUnit): def args_parser(self) -> ArgumentParserNoExit or None: @@ -1319,6 +1322,7 @@ def on_exec(self, args: argparse.Namespace): self.cmd.set_slot_enable(slot_num, sense_type, enable) print(f' - Set slot {slot_num} {"LF" if sense_type==chameleon_cmd.TagSenseType.TAG_SENSE_LF else "HF"} {"enable" if enable else "disable"} success.') + @lf_em_sim.command('set', 'Set simulated em410x card id') class LFEMSimSet(LFEMCardRequiredUnit): def args_parser(self) -> ArgumentParserNoExit or None: @@ -1690,19 +1694,23 @@ def on_exec(self, args: argparse.Namespace): class HF14ARaw(ReaderRequiredUnit): def bool_to_bit(self, value): - return 1 if value else 0 + return 1 if value else 0 def args_parser(self) -> ArgumentParserNoExit or None: parser = ArgumentParserNoExit() - parser.add_argument('-a', '--activate-rf', help="Active signal field ON without select", action='store_true', default=False,) - parser.add_argument('-s', '--select-tag', help="Active signal field ON with select", action='store_true', default=False,) + parser.add_argument('-a', '--activate-rf', help="Active signal field ON without select", + action='store_true', default=False,) + parser.add_argument('-s', '--select-tag', help="Active signal field ON with select", + action='store_true', default=False,) # TODO: parser.add_argument('-3', '--type3-select-tag', help="Active signal field ON with ISO14443-3 select (no RATS)", action='store_true', default=False,) parser.add_argument('-d', '--data', type=str, help="Data to be sent") parser.add_argument('-b', '--bits', type=int, help="Number of bits to send. Useful for send partial byte") parser.add_argument('-c', '--crc', help="Calculate and append CRC", action='store_true', default=False,) parser.add_argument('-r', '--response', help="Do not read response", action='store_true', default=False,) - parser.add_argument('-cc', '--crc-clear', help="Verify and clear CRC of received data", action='store_true', default=False,) - parser.add_argument('-k', '--keep-rf', help="Keep signal field ON after receive", action='store_true', default=False,) + parser.add_argument('-cc', '--crc-clear', help="Verify and clear CRC of received data", + action='store_true', default=False,) + parser.add_argument('-k', '--keep-rf', help="Keep signal field ON after receive", + action='store_true', default=False,) parser.add_argument('-t', '--timeout', type=int, help="Timeout in ms", default=100) # TODO: need support for carriage returns in parser, why are they mangled? # parser.description = 'Examples:\n' \ @@ -1712,7 +1720,6 @@ def args_parser(self) -> ArgumentParserNoExit or None: # ' hf 14a raw -sc -d 6000\n' return parser - def on_exec(self, args: argparse.Namespace): options = { 'activate_rf_field': self.bool_to_bit(args.activate_rf), @@ -1721,7 +1728,7 @@ def on_exec(self, args: argparse.Namespace): 'auto_select': self.bool_to_bit(args.select_tag), 'keep_rf_field': self.bool_to_bit(args.keep_rf), 'check_response_crc': self.bool_to_bit(args.crc_clear), - #'auto_type3_select': self.bool_to_bit(args.type3-select-tag), + # 'auto_type3_select': self.bool_to_bit(args.type3-select-tag), } data: str = args.data if data is not None: diff --git a/software/script/chameleon_cmd.py b/software/script/chameleon_cmd.py index acbb1df7..0e79eceb 100644 --- a/software/script/chameleon_cmd.py +++ b/software/script/chameleon_cmd.py @@ -164,7 +164,7 @@ class TagSpecificType(enum.IntEnum): ###### LF ###### - #### ASK Tag-Talk-First 100 + #### ASK Tag-Talk-First 100 #### # EM410x TAG_TYPE_EM410X = 100, # FDX-B @@ -177,18 +177,18 @@ class TagSpecificType(enum.IntEnum): # Noralsy # Jablotron - #### FSK Tag-Talk-First 200 + #### FSK Tag-Talk-First 200 #### # HID Prox # ioProx # AWID # Paradox - #### PSK Tag-Talk-First 300 + #### PSK Tag-Talk-First 300 #### # Indala # Keri # NexWatch - #### Reader-Talk-First 400 + #### Reader-Talk-First 400 #### # T5577 # EM4x05/4x69 # EM4x50/4x70 @@ -198,28 +198,28 @@ class TagSpecificType(enum.IntEnum): ###### HF ###### - # MIFARE Classic series 1000 + #### MIFARE Classic series 1000 #### TAG_TYPE_MIFARE_Mini = 1000, TAG_TYPE_MIFARE_1024 = 1001, TAG_TYPE_MIFARE_2048 = 1002, TAG_TYPE_MIFARE_4096 = 1003, - # MFUL / NTAG series 1100 + #### MFUL / NTAG series 1100 #### TAG_TYPE_NTAG_213 = 1100, TAG_TYPE_NTAG_215 = 1101, TAG_TYPE_NTAG_216 = 1102, - # MIFARE Plus series 1200 - # DESFire series 1300 + #### MIFARE Plus series 1200 #### + #### DESFire series 1300 #### - # ST25TA series 2000 + #### ST25TA series 2000 #### - # HF14A-4 series 3000 + #### HF14A-4 series 3000 #### @staticmethod def list(exclude_meta=True): return [t for t in TagSpecificType - if (t > TagSpecificType.OLD_TAG_TYPES_END and + if (t > TagSpecificType.OLD_TAG_TYPES_END and t != TagSpecificType.TAG_TYPES_LF_END) - or not exclude_meta] + or not exclude_meta] @staticmethod def list_hf(): @@ -628,7 +628,7 @@ def hf14a_raw(self, options, resp_timeout_ms=100, data=[], bitlen=None): :param bit_owned_by_the_last_byte: :return: """ - + class CStruct(ctypes.BigEndianStructure): _fields_ = [ ("activate_rf_field", ctypes.c_uint8, 1), @@ -1274,5 +1274,6 @@ def test_fn(): # disconnect dev.close() + if __name__ == '__main__': test_fn() From 2b4e8b29401bacabfcb11ab2d1b84c7bcd907a22 Mon Sep 17 00:00:00 2001 From: Philippe Teuwen Date: Mon, 25 Sep 2023 17:30:54 +0200 Subject: [PATCH 34/38] bump app version --- firmware/application/src/app_config.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/firmware/application/src/app_config.h b/firmware/application/src/app_config.h index a885958f..0d981ad7 100644 --- a/firmware/application/src/app_config.h +++ b/firmware/application/src/app_config.h @@ -2,7 +2,7 @@ // version code, 1byte, max = 0 -> 255 -#define APP_FW_VER_MAJOR 1 +#define APP_FW_VER_MAJOR 2 #define APP_FW_VER_MINOR 0 // Merge major and minor version code to U16 value. From 963d4d62f1bc6da5a079ab7c67317c2530d2af95 Mon Sep 17 00:00:00 2001 From: Philippe Teuwen Date: Mon, 25 Sep 2023 23:32:01 +0200 Subject: [PATCH 35/38] Deduce APP_FW_VER from git tag --- firmware/Makefile.defs | 5 ++++- firmware/application/Makefile | 7 +++++++ firmware/application/src/app_config.h | 7 +++++-- 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/firmware/Makefile.defs b/firmware/Makefile.defs index d345c8f5..7f7314dd 100644 --- a/firmware/Makefile.defs +++ b/firmware/Makefile.defs @@ -26,7 +26,10 @@ CHAMELEON_LITE := lite CURRENT_DEVICE_TYPE ?= ${CHAMELEON_ULTRA} # Versioning information -GIT_VERSION := "$(shell git describe --abbrev=7 --dirty --always --tags)" +GIT_VERSION := $(shell git describe --abbrev=7 --dirty --always --tags) +APP_FW_SEMVER := $(subst v,,$(shell git describe --tags --abbrev=0 --match "v*.*")) +APP_FW_VER_MAJOR := $(word 1,$(subst ., ,$(APP_FW_SEMVER))) +APP_FW_VER_MINOR := $(word 2,$(subst ., ,$(APP_FW_SEMVER))) # Enable NRF_LOG on SWO pin as UART TX NRF_LOG_UART_ON_SWO_ENABLED := 0 diff --git a/firmware/application/Makefile b/firmware/application/Makefile index 9e02889d..632b3e28 100644 --- a/firmware/application/Makefile +++ b/firmware/application/Makefile @@ -401,6 +401,13 @@ CFLAGS += -fno-builtin -fshort-enums # Versioning flags CFLAGS += -DGIT_VERSION=\"$(GIT_VERSION)\" +ifndef APP_FW_VER_MAJOR +$(error APP_FW_VER_MAJOR is not defined, please define it) +endif +ifndef APP_FW_VER_MINOR +$(error APP_FW_VER_MINOR is not defined, please define it) +endif +CFLAGS += -DAPP_FW_VER_MAJOR=$(APP_FW_VER_MAJOR) -DAPP_FW_VER_MINOR=$(APP_FW_VER_MINOR) # C++ flags common to all targets CXXFLAGS += $(OPT) diff --git a/firmware/application/src/app_config.h b/firmware/application/src/app_config.h index 0d981ad7..5dc7023b 100644 --- a/firmware/application/src/app_config.h +++ b/firmware/application/src/app_config.h @@ -2,8 +2,11 @@ // version code, 1byte, max = 0 -> 255 -#define APP_FW_VER_MAJOR 2 -#define APP_FW_VER_MINOR 0 +//#define APP_FW_VER_MAJOR 2 +//#define APP_FW_VER_MINOR 0 +#if !(defined APP_FW_VER_MAJOR && defined APP_FW_VER_MINOR) + #error You need to define APP_FW_VER_MAJOR and APP_FW_VER_MINOR +#endif // Merge major and minor version code to U16 value. #define FW_VER_NUM VER_CODE_TO_NUM(APP_FW_VER_MAJOR, APP_FW_VER_MINOR) From b03ef3ec21ff301f89daa96f86572b526616d273 Mon Sep 17 00:00:00 2001 From: Philippe Teuwen Date: Tue, 26 Sep 2023 00:08:33 +0200 Subject: [PATCH 36/38] build_firmware.yml: fetch depth=0 to be able to build proper version strings --- .github/workflows/build_firmware.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build_firmware.yml b/.github/workflows/build_firmware.yml index b584580d..8639bcea 100644 --- a/.github/workflows/build_firmware.yml +++ b/.github/workflows/build_firmware.yml @@ -23,6 +23,7 @@ jobs: uses: actions/checkout@v3 with: ref: ${{ inputs.checkout-sha == null && github.sha || inputs.checkout-sha }} + fetch-depth: 0 - name: ghcr.io login uses: docker/login-action@v2 with: @@ -56,6 +57,7 @@ jobs: uses: actions/checkout@v3 with: ref: ${{ inputs.checkout-sha == null && github.sha || inputs.checkout-sha }} + fetch-depth: 0 - name: Build firmware env: repo: ${{ github.repository }} From b9e5c95480eb8bd09ec42daf49f2a2c6e1ba0862 Mon Sep 17 00:00:00 2001 From: Philippe Teuwen Date: Tue, 26 Sep 2023 08:42:31 +0200 Subject: [PATCH 37/38] changelog --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f32d1af..76153192 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,10 +3,16 @@ All notable changes to this project will be documented in this file. This project uses the changelog in accordance with [keepchangelog](http://keepachangelog.com/). Please use this to write notable changes, which is not the same as git commit log... ## [unreleased][unreleased] + +## [v2.0.0][2023-09-26] + - Changed APP_FW_VER now deduced from git tag vx.y.z (@doegox) + - Changed initial button wakeup from 4 to 8 seconds (@aramova) - Added MIFARE Ultralight reading features (@FlUxIuS & @doegox) - Fixed MF1 write mode SHADOW was not preserved properly (@doegox) + - Changed field LED now active also in reader mode to indicate that reader is powering the field (@doegox) - Changed slot enabled logic: now we have separate enabled_hf and enabled_lf, changed GET_ENABLED_SLOTS and SET_SLOT_ENABLE (@doegox) - Changed tag type enum to be ready for new types, changed stored slotConfig and GET_SLOT_INFO (@doegox) + - Added HF14A_RAW and its support in `hf 14a raw` (@xianglin1998) - Removed MF1_DETECT_DARKSIDE (@doegox) - Added MF1_STATIC_NESTED_ACQUIRE and its support in `hf mf nested` (@xianglin1998) - Changed `hf 14a scan`: Automatically send RATS to 14443-4a tags (@augustozanellato) From 099fb6b912eaf947fed736c7ed8970972c3308c5 Mon Sep 17 00:00:00 2001 From: Philippe Teuwen Date: Tue, 26 Sep 2023 09:28:42 +0200 Subject: [PATCH 38/38] CI for releases --- .github/workflows/on_push.yml | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/.github/workflows/on_push.yml b/.github/workflows/on_push.yml index d5ef5dea..08c228ed 100644 --- a/.github/workflows/on_push.yml +++ b/.github/workflows/on_push.yml @@ -13,7 +13,7 @@ jobs: client_pipeline: name: Build Firmware uses: ./.github/workflows/build_client.yml - create_release: + create_dev_release: permissions: contents: write name: Create dev pre-release with artifacts @@ -49,3 +49,32 @@ jobs: run: | git tag -f dev git push --tags -f + create_release: + permissions: + contents: write + name: Create tagged release with artifacts + runs-on: ubuntu-latest + if: startsWith(github.ref, 'refs/tags/v') + needs: + - firmware_pipeline + - client_pipeline + steps: + - name: Check out the repo + uses: actions/checkout@v3 + - name: Download release artifacts + uses: actions/download-artifact@v3 + with: + name: release-artifacts + path: release-artifacts + - name: Upload to tagged release + uses: softprops/action-gh-release@v1 + with: + body: | + Auto-Generated DFU packages for Release ${{ github.ref_name }} + Built from commit ${{ github.sha }} + name: Release ${{ github.ref_name }} + draft: false + target_commitish: ${{ github.sha }} + generate_release_notes: true + append_body: true + files: release-artifacts/*