Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ARM64, Nougat and NXP compatibility #97

Closed
wants to merge 34 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
feb27a4
Updated gradle, SDK to 26, fixed compiler issues
kleest Jan 19, 2018
0f37b07
Updated build tools, arch detection, arm64 support
roussosalex Jan 19, 2018
b9e0729
Fixed nougat compatibility
roussosalex Jan 19, 2018
4608d89
Fixed class path issues, enabled hooking
roussosalex Jan 19, 2018
fce87bd
Fixed compatibility for pre-nougat
roussosalex Jan 19, 2018
081bba2
Fixed arm, arm64 progress
roussosalex Dec 8, 2017
b984fa6
Refactored hook code to ease future development
roussosalex Dec 8, 2017
e1babb9
Added symbol size check, prevent overwrite
roussosalex Dec 13, 2017
979bbf3
Improved armv8 trampoline
roussosalex Dec 13, 2017
eba562c
Refactored configuration stream handling
kleest Jan 19, 2018
fd50e99
Fixed sleep, clone restart, ATQA
kleest Jan 19, 2018
71dd567
Added workaround for NetworkOnMainThreadException
kleest Jan 19, 2018
be158de
Added advanced cache flushing for ARM64
kleest Jan 19, 2018
ab4a188
Restructured config related code
kleest Jan 26, 2018
621afcd
Switched from SingleTop to SingleTask mode
roussosalex Mar 5, 2018
e742f90
Readded loghex debug helper function
kleest Feb 21, 2018
6a83ef9
Restructured nfcd and added actual android headers
roussosalex Feb 26, 2018
c37ba68
Generalized anticol data for all NFC Types
roussosalex Feb 27, 2018
62b5bb1
Completed IsoDep support
kleest Feb 27, 2018
bedc1f9
Fixed typos in NfcV reader
kleest Feb 27, 2018
3780c73
Minor code cleanup
kleest Feb 27, 2018
4f82567
Switched to new protobuf version, regened protocol
kleest Feb 27, 2018
d0dc522
Re-enabled network
kleest Feb 27, 2018
c40a14d
Fixed database logging
roussosalex Feb 27, 2018
4b6d97e
Added logging to TCP handler
roussosalex Feb 27, 2018
f35ca8f
Pin UID as soon as tag is discovered in clone mode
roussosalex Feb 28, 2018
2320743
License consolidation
kleest Mar 8, 2018
49d28fe
Updated README
kleest Mar 8, 2018
b59c20a
Fixed UI threading bug
roussosalex May 16, 2018
3dcba2a
Fixed ENABLE_POLLING bug
roussosalex May 16, 2018
7087b83
Fixed crash if NFC NCI stack does not exist
kleest Jun 5, 2018
4ec9925
Fixed session logging
kleest Jun 5, 2018
211a384
NFCGate is a payment application now
roussosalex Oct 12, 2018
f439142
Revert "NFCGate is a payment application now"
roussosalex Oct 30, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 6 additions & 0 deletions AUTHORS
@@ -0,0 +1,6 @@
Steffen Klee
Max Maass
Uwe Müller
Alexandros Roussos
Tom Schons
Daniel Wegemer
2 changes: 1 addition & 1 deletion LICENSE
@@ -1,4 +1,4 @@
Copyright 2015, NFCGate Team
Copyright 2015-2018, NFCGate Team

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down
23 changes: 14 additions & 9 deletions README.md
Expand Up @@ -38,32 +38,37 @@ Once you are done, you can remove the card and reader, and disconnect from the s
There are some caveats, so read this section carefully before using the application (and especially before filing a bug report).

### Native code patch compatibility
Our patch to the android NFC Daemon only works with devices using a Broadcom NFC chip and `libnfc-nci` (not `libnfc-nxp`). If you are unsure what Chip your device is using, take a look at `/dev`: Broadcom devices are usually start with `bcm`, while the most common NXP chip is `pn544`. Our code has been successfully tested on the Nexus 4 and 5, and **should** work on other devices using the same libnfc and a compatible Broadcom Chip. On incompatible devices, the application may still start, but it will be unable to proxy commands from the NFC reader. This is due to limitations in the Android API.
Our patch to the android NFC Daemon only works with devices using either a Broadcom or NXP NFC chip. If you are unsure what Chip your device is using, take a look at `/dev`: Broadcom devices are usually start with `bcm`, while the most common NXP chip is `pn544`. Our code has been successfully tested on the Nexus 4, 5, and 5X, and **should** work on other devices using the same libnfc and a compatible NFC chip. On incompatible devices, the application may still start, but it will be unable to proxy commands from the NFC reader. This is due to limitations in the Android API.

The patch is also only compatible with devices that can run Xposed. For Android 4.4.X, this requires a rooted device. Android 5.X devices do not need root, but the installation procedure is more complex and requires flashing the device.
The patch is also only compatible with devices that can run Xposed. For Android 4.4.X, this requires a rooted device. Android 5+ devices do not need root, but the installation procedure is more complex and requires flashing the device.

We currently use the "Android Dynamic Binary Instrumentation Toolkit" (see below) for our native code patches. ADBI does not support ARM64 at the moment. This means that our patches do not work on smartphones with an ARM64 CPU (like the Nexus 5X). Maybe [this](https://github.com/Samsung/ADBI) is an alternative for the future.
NFCGate supports ARM, THUMB, and ARM64 ABIs only.

### Android version compatibility
NFCGate is successfully tested on Android 4.4, 6, 7, 7.1. Any other version remains untested.

### DESFire workaround
The Android NFC Libraries contain a bug which makes it impossible to use our application with MiFare DESFire cards (a common NFC card for payment systems). We are using a workaround to enable us to still read these cards, but that workaround has some side effects. When you start the application, you will get a warning. Please read the information carefully.

### Compatibility with cards
Android no longer offers support for MiFare classic chips on many devices. In general, we can only proxy tags supported by Android. When in doubt, use an application like NFC Tag info to find out if your tag is compatible. We have done extensive testing with MiFare DESFire cards using a Nexus S, Nexus 4 and Nexus 5 as reader, and a Nexus 4 or Nexus 5 as HCE phone. All other combinations are untested (feedback is welcome).
Android no longer offers support for MiFare classic chips on many devices. In general, we can only proxy tags supported by Android. When in doubt, use an application like NFC Tag info to find out if your tag is compatible. We have done extensive testing with MiFare DESFire cards (NFC-A) using a Nexus S, 4, 5, and 5X as reader, and a Nexus 4, 5, or 5X as HCE phone. All other combinations are untested (feedback is welcome).

Also, at the moment, only NFC-A tags are implemented. They are the most common tags (for example, both the MiFare DESFire and specialized chips like the ones in electronic passports use NFC-A, but contactless credit cards use NFC-B and are thus incompatible right now), but you may experience problems if you use other tags. A common symptom is that the app restarts and pops back up without any UI elements shown. We'll try to get support for other NFC-Versions in ASAP, but since we don't have any cards that use these standards, we can't test them.
Also, at the moment, every tag technology supported by Android's HCE is supported (A, B, F), however NFC-B and NFC-F remain untested. NFC-A tags are the most common tags (for example, both the MiFare DESFire and specialized chips like the ones in electronic passports use NFC-A), but you may experience problems if you use other tags.

### Compatibility with readers
This application only works with readers which do not implement additional security measures. One security measure which will prevent our application from working is when the reader checks the time it takes the card to respond (or, to use the more general case, if the reader implements "distance bounding"). The network transmission adds a noticeable delay to any transaction, so any secure reader will not accept our proxied replies. However, if the reader does not implement any additional checks, it *should*be possible to proxy it.

### Android NFC limitations
Some features of NFC are not supported by android and thus cannot be used with our application. These features include *extended length APDUs*. We have also experienced cases where the NFC field generated by the phone was not strong enough to properly power more advanced features of some NFC chips (e.g. cryptographic operations). Keep this in mind if you are testing chips we have not experimented with.
Some features of NFC are not supported by Android and thus cannot be used with our application. These features include *extended length APDUs*. We have also experienced cases where the NFC field generated by the phone was not strong enough to properly power more advanced features of some NFC chips (e.g. cryptographic operations). Keep this in mind if you are testing chips we have not experimented with.

### Confidentiality of data channel
Right now, all data is sent *unencrypted* over the network. We may or may not get around to implementing cryptographic protection, but for now, consider everything you send over the network to be readable by anyone interested, unless you use extra protection like VPNs. Keep that in mind while performing your own tests.
Right now, all data in relay mode is sent *unencrypted* over the network. We may or may not get around to implementing cryptographic protection, but for now, consider everything you send over the network to be readable by anyone interested, unless you use extra protection like VPNs. Keep that in mind while performing your own tests.

## Used Libraries
This application uses the following external libraries:
- [Xposed](http://repo.xposed.info/) (Licensed under the [Apache License v2.0](http://opensource.org/licenses/Apache-2.0))
- [LibNFC](https://android.googlesource.com/platform/external/libnfc-nci/) (Licensed under the [Apache License v2.0](http://opensource.org/licenses/Apache-2.0))
- [LibNFC-NCI](https://android.googlesource.com/platform/external/libnfc-nci/) (Licensed under the [Apache License v2.0](http://opensource.org/licenses/Apache-2.0))
- [Protobuf](https://code.google.com/p/protobuf/) (Licensed under the [BSD 3-Clause license](http://opensource.org/licenses/BSD-3-Clause))
- [ADBI](https://github.com/crmulliner/adbi) (no licence available)

## Credits
- [ADBI](https://github.com/crmulliner/adbi): ARM and THUMB inline hooking
10 changes: 5 additions & 5 deletions app/build.gradle
@@ -1,13 +1,13 @@
apply plugin: 'com.android.application'

android {
compileSdkVersion 23
buildToolsVersion '25.0.0'
compileSdkVersion 25
buildToolsVersion '27.0.1'

defaultConfig {
applicationId "tud.seemuh.nfcgate"
minSdkVersion 19
targetSdkVersion 22
targetSdkVersion 25
versionCode 3
versionName "1.3.1"
}
Expand All @@ -23,6 +23,6 @@ dependencies {
//This must NOT be included
//compile fileTree(dir: 'libs', include: ['*.jar'])
compile project(':nfcd')
compile files('libs/protobuf-java-2.6.1.jar')
compile 'com.android.support:support-v4:23.0.0'
compile 'com.google.protobuf:protobuf-java:3.5+'
compile 'com.android.support:support-v4:25.4.0'
}
Binary file removed app/libs/protobuf-java-2.6.1.jar
Binary file not shown.
6 changes: 3 additions & 3 deletions app/src/main/AndroidManifest.xml
Expand Up @@ -38,12 +38,14 @@
<activity
android:name=".gui.MainActivity"
android:label="@string/app_name"
android:launchMode="singleTop"
android:launchMode="singleTask"
android:screenOrientation="portrait" >
<intent-filter>
<action android:name="tud.seemuh.nfcgate.main" />

<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
Expand All @@ -52,9 +54,7 @@
android:screenOrientation="portrait" >
<intent-filter>
<action android:name="tud.seemuh.nfcgate.splash" />
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

Expand Down
Expand Up @@ -15,6 +15,7 @@
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.CompoundButton;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.Switch;
Expand All @@ -34,7 +35,7 @@
import tud.seemuh.nfcgate.util.sink.SinkManager;
import tud.seemuh.nfcgate.xposed.Native;

public class CloneFragment extends Fragment implements OnClickListener {
public class CloneFragment extends Fragment implements CompoundButton.OnCheckedChangeListener {

private final static String TAG = "CloneFragment";

Expand All @@ -61,10 +62,10 @@ public View onCreateView(final LayoutInflater inflater, ViewGroup container, Bun
final View v = inflater.inflate(R.layout.fragment_clone, container, false);
mCurrUID = (TextView) v.findViewById(R.id.cloned_uid);
mToggleCloneMode = (Switch) v.findViewById(R.id.btnSwitchCloneMode);
mToggleCloneMode.setOnClickListener(this);
mToggleCloneMode.setOnCheckedChangeListener(this);

mPinUID = (Switch) v.findViewById(R.id.btnSwitchPinUID);
mPinUID.setOnClickListener(this);
mPinUID.setOnCheckedChangeListener(this);
mPinUID.setClickable(false);

mListView = (ListView) v.findViewById(R.id.savedList);
Expand Down Expand Up @@ -158,14 +159,10 @@ public static CloneFragment getInstance() {
}

@Override
public void onClick(View v) {
boolean on;

switch(v.getId()) {
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
switch (buttonView.getId()) {
case R.id.btnSwitchCloneMode:
on = ((Switch) v).isChecked();

if (on) {
if (isChecked) {
//set sink
try {
mSinkManager = new SinkManager(mSinkManagerQueue);
Expand All @@ -186,30 +183,35 @@ public void onClick(View v) {
mNfcManager.shutdown();
mCloneModeEnabled = false;

DaemonConfiguration.getInstance().enablePolling();
mPinUID.setChecked(false);
mPinUID.setClickable(false);
mPinUID.setChecked(false);
}

break;
case R.id.btnSwitchPinUID:
on = ((Switch) v).isChecked();

if(on) {
if(isChecked) {
Log.i(TAG, "onClick(): PinUID on");
DaemonConfiguration.getInstance().disablePolling();
} else {
Log.i(TAG, "onClick(): PinUID off");
DaemonConfiguration.getInstance().enablePolling();
}

break;
}
}

public void onTagDiscoveredCommon(Tag tag) {
if(mCloneModeEnabled) {
//this call notifies the TextSink 2x: ok here, we override it anyway
RelayFragment.getInstance().mNfcManager.setAnticolData(RelayFragment.getInstance().mNfcManager.getAnticolData());
mSaveButton.setVisibility(View.VISIBLE);

// pin uid as soon as tag was discovered
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
getView().findViewById(R.id.btnSwitchPinUID).performClick();
}
});
}
}
}
Expand Up @@ -26,6 +26,7 @@
import java.util.List;

import tud.seemuh.nfcgate.R;
import tud.seemuh.nfcgate.nfc.config.ConfigBuilder;
import tud.seemuh.nfcgate.util.NfcComm;
import tud.seemuh.nfcgate.util.NfcSession;
import tud.seemuh.nfcgate.util.db.SessionLoggingContract;
Expand Down Expand Up @@ -257,15 +258,9 @@ protected Cursor doInBackground(Long... SessionID) {
SessionLoggingContract.SessionEvent.COLUMN_NAME_DATE,
SessionLoggingContract.SessionEvent.COLUMN_NAME_TYPE,
SessionLoggingContract.SessionEvent.COLUMN_NAME_NFCDATA,
SessionLoggingContract.SessionEvent.COLUMN_NAME_UID,
SessionLoggingContract.SessionEvent.COLUMN_NAME_ATQA,
SessionLoggingContract.SessionEvent.COLUMN_NAME_SAK,
SessionLoggingContract.SessionEvent.COLUMN_NAME_HIST,
SessionLoggingContract.SessionEvent.COLUMN_NAME_CONFIG,
SessionLoggingContract.SessionEvent.COLUMN_NAME_NFCDATA_PREFILTER,
SessionLoggingContract.SessionEvent.COLUMN_NAME_UID_PREFILTER,
SessionLoggingContract.SessionEvent.COLUMN_NAME_ATQA_PREFILTER,
SessionLoggingContract.SessionEvent.COLUMN_NAME_SAK_PREFILTER,
SessionLoggingContract.SessionEvent.COLUMN_NAME_HIST_PREFILTER,
SessionLoggingContract.SessionEvent.COLUMN_NAME_CONFIG_PREFILTER,
};
// Define Sort order
String sortorder = SessionLoggingContract.SessionEvent._ID + " ASC";
Expand Down Expand Up @@ -307,20 +302,14 @@ protected void onPostExecute(Cursor c) {
int type = c.getInt(c.getColumnIndexOrThrow(SessionLoggingContract.SessionEvent.COLUMN_NAME_TYPE));
if (type == SessionLoggingContract.SessionEvent.VALUE_TYPE_ANTICOL) {
// Anticol
byte[] uid = c.getBlob(c.getColumnIndexOrThrow(SessionLoggingContract.SessionEvent.COLUMN_NAME_UID));
byte[] atqa = c.getBlob(c.getColumnIndexOrThrow(SessionLoggingContract.SessionEvent.COLUMN_NAME_ATQA));
byte[] sak = c.getBlob(c.getColumnIndexOrThrow(SessionLoggingContract.SessionEvent.COLUMN_NAME_SAK));
byte[] hist = c.getBlob(c.getColumnIndexOrThrow(SessionLoggingContract.SessionEvent.COLUMN_NAME_HIST));
byte[] uid_pf = c.getBlob(c.getColumnIndexOrThrow(SessionLoggingContract.SessionEvent.COLUMN_NAME_UID_PREFILTER));
byte[] atqa_pf = c.getBlob(c.getColumnIndexOrThrow(SessionLoggingContract.SessionEvent.COLUMN_NAME_ATQA_PREFILTER));
byte[] sak_pf = c.getBlob(c.getColumnIndexOrThrow(SessionLoggingContract.SessionEvent.COLUMN_NAME_SAK_PREFILTER));
byte[] hist_pf = c.getBlob(c.getColumnIndexOrThrow(SessionLoggingContract.SessionEvent.COLUMN_NAME_HIST_PREFILTER));
if (uid_pf != null) {
byte[] config = c.getBlob(c.getColumnIndexOrThrow(SessionLoggingContract.SessionEvent.COLUMN_NAME_CONFIG));
byte[] config_pf = c.getBlob(c.getColumnIndexOrThrow(SessionLoggingContract.SessionEvent.COLUMN_NAME_CONFIG_PREFILTER));
if (config_pf != null) {
// Saving prefilter data is all-or-nothing: If one value is changed, all are saved
// We now need to properly initialize the NfcComm object.
comm = new NfcComm(atqa, sak[0], hist, uid, atqa_pf, sak_pf[0], hist_pf, uid_pf);
comm = new NfcComm(new ConfigBuilder(config), new ConfigBuilder(config_pf));
} else {
comm = new NfcComm(atqa, sak[0], hist, uid);
comm = new NfcComm(new ConfigBuilder(config));
}

} else {
Expand Down
Expand Up @@ -438,17 +438,11 @@ public void sendAnticol(NfcComm nfcdata) {
}

// Retrieve values
byte[] atqa = nfcdata.getAtqa();
byte sak = nfcdata.getSak();
byte[] hist = nfcdata.getHist();
byte[] uid = nfcdata.getUid();
byte[] config = nfcdata.getConfig().build();

// Build reply protobuf
C2C.Anticol.Builder b = C2C.Anticol.newBuilder();
b.setATQA(ByteString.copyFrom(atqa));
b.setSAK(ByteString.copyFrom(new byte[]{sak}));
b.setHistoricalByte(ByteString.copyFrom(hist));
b.setUID(ByteString.copyFrom(uid));
b.setCONFIG(ByteString.copyFrom(config));

// TODO If we aren't in a session, cache this and send it as soon as a session is established?
// (And delete it if the card is removed in the meantime)
Expand Down
Expand Up @@ -109,13 +109,22 @@ public void run() {

//read answer from SOCKET
mRunnableComThread = new CommunicationThread(mSocket);
synchronized (mSendQueueSync) {
commThread = new Thread(mRunnableComThread);
commThread.start();
for(byte[] msg : mSendQueue) {
reallySendBytes(msg);
commThread = new Thread(mRunnableComThread);
commThread.start();

while (true) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}

synchronized (mSendQueueSync) {
for (byte[] msg : mSendQueue) {
reallySendBytes(msg);
}
mSendQueue.clear();
}
mSendQueue.clear();
}

} catch (ConnectException e1) {
Expand All @@ -140,13 +149,8 @@ public synchronized byte[] getBytesFromNW() {

public void sendBytes(byte[] msg) {
synchronized (mSendQueueSync) {
// If the communication Thread has not started up yet, save the message for later.
if(commThread == null) {
mSendQueue.add(msg);
return;
}
mSendQueue.add(msg);
}
reallySendBytes(msg);
}

private void reallySendBytes(byte[] msg) {
Expand Down Expand Up @@ -230,6 +234,7 @@ public void run() {
}
}
} catch (IOException e) {
Log.i(TAG, "IOERROR ", e);
if (mCallback != null) {
mCallback.notifyBrokenPipe();
return;
Expand Down