Skip to content

Commit

Permalink
add Xposed hook for android nfc service
Browse files Browse the repository at this point in the history
update AGP version
  • Loading branch information
cinit committed Dec 20, 2021
1 parent 8fcbc96 commit 3586577
Show file tree
Hide file tree
Showing 9 changed files with 249 additions and 1 deletion.
1 change: 1 addition & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ dependencies {
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2"
implementation 'com.google.code.gson:gson:2.8.9'
implementation 'androidx.preference:preference-ktx:1.1.1'
compileOnly("de.robv.android.xposed:api:82")
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
Expand Down
13 changes: 13 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,19 @@
</intent-filter>
</receiver>

<meta-data
android:name="xposedmodule"
android:value="true" />
<meta-data
android:name="xposeddescription"
android:value="@string/xposeddescription" />
<meta-data
android:name="xposedminversion"
android:value="51" />
<meta-data
android:name="xposedscope"
android:resource="@array/xposedscope" />

<meta-data
android:name="com.sec.android.support.multiwindow"
android:value="true" />
Expand Down
1 change: 1 addition & 0 deletions app/src/main/assets/xposed_init
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
cc.ioctl.nfcdevicehost.xposed.HookEntry
30 changes: 30 additions & 0 deletions app/src/main/java/cc/ioctl/nfcdevicehost/xposed/HookEntry.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package cc.ioctl.nfcdevicehost.xposed;

import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.callbacks.XC_LoadPackage;

public class HookEntry implements IXposedHookLoadPackage {

private static void initNfcServiceHook(ClassLoader classLoader) {
XposedHelpers.findAndHookMethod("com.android.nfc.NfcService", classLoader, "playSound", int.class, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) {
if (SysNfcSvcPatch.sDisableNfcDiscoverySound) {
param.setResult(null);
}
}
});
SysNfcSvcPatch.startMainThread();
}

@Override
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) {
if ("com.android.nfc".equals(lpparam.packageName)) {
if (lpparam.isFirstApplication) {
initNfcServiceHook(lpparam.classLoader);
}
}
}
}
194 changes: 194 additions & 0 deletions app/src/main/java/cc/ioctl/nfcdevicehost/xposed/SysNfcSvcPatch.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
package cc.ioctl.nfcdevicehost.xposed;

import android.net.Credentials;
import android.net.LocalServerSocket;
import android.net.LocalSocket;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import de.robv.android.xposed.XposedBridge;

public class SysNfcSvcPatch {

private static Thread sWorkingThread = null;
public static boolean sDisableNfcDiscoverySound = false;
private static boolean sShouldRunning = true;

static void run() {
try {
int myPid = android.os.Process.myPid();
while (sShouldRunning) {
LocalServerSocket localServerSocket;
try {
localServerSocket = new LocalServerSocket("com.android.nfc/cc.ioctl.nfcdevicehost.xposed.SysNfcSvcPatch@" + myPid);
while (sShouldRunning) {
LocalSocket localSocket = localServerSocket.accept();
Credentials cred = null;
try {
cred = localSocket.getPeerCredentials();
} catch (IOException ignored) {
}
if (cred != null && cred.getUid() == 0) {
localServerSocket.close();
handleTransaction(localSocket);
// socket is closed in handleTransaction
break;
} else {
try {
localSocket.close();
} catch (IOException ignored) {
}
}
}
} catch (IOException e) {
XposedBridge.log("SysNfcSvcPatch: " + e.getMessage());
XposedBridge.log(e);
return;
}
}
} catch (RuntimeException e) {
XposedBridge.log(e);
}
}

static final int TYPE_REQUEST = 1;
static final int TYPE_RESPONSE = 2;
static final int TYPE_EVENT = 3;
static final byte[] EMPTY_BYTE_ARRAY = new byte[0];

static final int REQUEST_SET_NFC_SOUND_DISABLE = 0x30;
static final int REQUEST_GET_NFC_SOUND_DISABLE = 0x31;

static void handleTransaction(LocalSocket socket) {
try {
byte[] buffer = new byte[65536];
InputStream is = socket.getInputStream();
while (sShouldRunning) {
// read 4 bytes length
readFully(is, buffer, 0, 4);
int length = getInt32Le(buffer, 0);
if (length < 16 || length > 65535) {
break;
}
// read length bytes
readFully(is, buffer, 0, length);
// uint32_t type
int type = getInt32Le(buffer, 0);
// uint32_t sequence
int sequence = getInt32Le(buffer, 4);
// uint32_t requestCode
int requestCode = getInt32Le(buffer, 8);
// uint32_t arg0
int arg0 = getInt32Le(buffer, 12);
byte[] data;
if (length > 16) {
data = new byte[length - 16];
System.arraycopy(buffer, 16, data, 0, length - 16);
} else {
data = EMPTY_BYTE_ARRAY;
}
switch (type) {
case TYPE_REQUEST:
handleRequest(socket, sequence, requestCode, arg0, data);
break;
case TYPE_RESPONSE:
case TYPE_EVENT:
default:
break;
}
}
} catch (IOException e) {
XposedBridge.log("SysNfcSvcPatch: " + e.getMessage());
}
try {
socket.close();
} catch (IOException ignored) {
}
}

private static void handleRequest(LocalSocket socket, int sequence, int requestCode, int arg0, byte[] data) {
switch (requestCode) {
case REQUEST_SET_NFC_SOUND_DISABLE: {
sDisableNfcDiscoverySound = (arg0 != 0);
sendResponse(socket, sequence, 0, 0, null);
break;
}
case REQUEST_GET_NFC_SOUND_DISABLE: {
sendResponse(socket, sequence, sDisableNfcDiscoverySound ? 1 : 0, 0, null);
break;
}
default: {
sendResponse(socket, sequence, -1, -1, null);
}
}
}

static void sendResponse(LocalSocket socket, int sequence, int resultCode, int errCode, byte[] data) {
if (data == null) {
data = EMPTY_BYTE_ARRAY;
}
byte[] bufferLen4 = new byte[4];
putInt32Le(bufferLen4, 0, data.length + 16);
byte[] buffer = new byte[16 + data.length];
putInt32Le(buffer, 0, TYPE_RESPONSE);
putInt32Le(buffer, 4, sequence);
putInt32Le(buffer, 8, resultCode);
putInt32Le(buffer, 12, errCode);
System.arraycopy(data, 0, buffer, 16, data.length);
try {
OutputStream os = socket.getOutputStream();
os.write(bufferLen4);
os.write(buffer);
os.flush();
} catch (IOException e) {
XposedBridge.log("SysNfcSvcPatch: " + e.getMessage());
}
}

public static synchronized void startMainThread() {
if (sWorkingThread == null || !sWorkingThread.isAlive()) {
sWorkingThread = new Thread(SysNfcSvcPatch::run);
sWorkingThread.start();
}
}

static void readFully(InputStream is, byte[] buffer, int offset, int length) throws IOException {
int remaining = length;
while (remaining > 0) {
int read = is.read(buffer, offset, remaining);
if (read < 0) {
throw new IOException("Unexpected EOF");
}
remaining -= read;
offset += read;
}
}

/**
* Read a little-endian 32-bit integer from the given byte array.
*
* @param buffer The byte array to read from.
* @param offset The offset to start reading from.
* @return The 32-bit integer read.
*/
static int getInt32Le(byte[] buffer, int offset) {
return (buffer[offset] & 0xFF) | ((buffer[offset + 1] & 0xFF) << 8)
| ((buffer[offset + 2] & 0xFF) << 16) | ((buffer[offset + 3] & 0xFF) << 24);
}

/**
* Put a little-endian 32-bit integer to the given byte array.
*
* @param buffer The byte array to write to.
* @param offset The offset to start writing to.
* @param value the value to put
*/
static void putInt32Le(byte[] buffer, int offset, int value) {
buffer[offset] = (byte) (value & 0xFF);
buffer[offset + 1] = (byte) ((value >> 8) & 0xFF);
buffer[offset + 2] = (byte) ((value >> 16) & 0xFF);
buffer[offset + 3] = (byte) ((value >> 24) & 0xFF);
}
}
1 change: 1 addition & 0 deletions app/src/main/res/values-zh-rCN/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -70,5 +70,6 @@ NCI Host 的开发人员不对使用此应用程序造成的任何损失负责
<string name="action_decoder_config">解码选项</string>
<string name="ui_hal_dump_decoder_config_title">解码选项</string>
<string name="action_confirm">确认</string>
<string name="xposeddescription">NFC 系统服务附加功能</string>

</resources>
6 changes: 6 additions & 0 deletions app/src/main/res/values/arrays.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,14 @@
<item>reply</item>
<item>reply_all</item>
</string-array>

<string-array name="ui_hal_dump_decoder_config_items">
<item>None(raw syscall dump)</item>
<item>NCI protocol</item>
</string-array>

<string-array name="xposedscope">
<item>com.android.nfc</item>
</string-array>

</resources>
1 change: 1 addition & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -99,4 +99,5 @@ By using this application, you accept all responsibility using it and agree that
<string name="action_decoder_config">Decoder properties</string>
<string name="ui_hal_dump_decoder_config_title">Decoder Properties</string>
<string name="action_confirm">Confirm</string>
<string name="xposeddescription">Extra features for NFC service.</string>
</resources>
3 changes: 2 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.0.3'
classpath 'com.android.tools.build:gradle:7.0.4'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
Expand All @@ -19,6 +19,7 @@ allprojects {
google()
mavenCentral()
maven { url 'https://jitpack.io' }
maven { url 'https://api.xposed.info/' }
}
}

Expand Down

0 comments on commit 3586577

Please sign in to comment.