Android8.1获取Wifi,BT,Ethernet MAC地址问题分析

Jelena ·
更新时间:2024-09-21
· 662 次阅读

1.Wifi MAC地址

不说废话,直接上代码:

public String getWifiMac() { String wifiMac = ""; try { WifiManager wifi = (WifiManager) getSystemService(Context.WIFI_SERVICE); wifiMac = wifi.getConnectionInfo().getMacAddress(); Log.d(TAG, "wifiMac: " + wifiMac); } catch (Exception e) { e.printStackTrace(); } return wifiMac; }

那么问题来了,这份代码在Android5.1上跑的好好的,在Android8.1上,拿到的却是一个固定地址02:00:00:00:00:00。
估计是Android新特性,不让第三方应用拿MAC地址了。我们阅读源码,证实一下。
frameworks/base/wifi/java/android/net/wifi/WifiManager.java

public WifiInfo getConnectionInfo() { try { //这里没做啥事,只是封装了一下,调到服务端去了。 return mService.getConnectionInfo(getContext().getOpPackageName()); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } }

frameworks/opt/net/wifi/service/java/com/android/server/wifi/WifiServiceImpl.java

@Override public WifiInfo getConnectionInfo(String callingPackage) { enforceAccessPermission();//权限检查 mLog.info("getConnectionInfo uid=%").c(Binder.getCallingUid()).flush(); /* * Make sure we have the latest information, by sending * a status request to the supplicant. */ //继续往下,调WifiStateMachine return mWifiStateMachine.syncRequestConnectionInfo(callingPackage); } //要声明android.permission.ACCESS_WIFI_STATE权限 private void enforceAccessPermission() { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_WIFI_STATE, "WifiService"); }

frameworks/opt/net/wifi/service/java/com/android/server/wifi/WifiStateMachine.java

public WifiInfo syncRequestConnectionInfo(String callingPackage) { int uid = Binder.getCallingUid(); WifiInfo result = new WifiInfo(mWifiInfo);// new了一个WifiInfo实例 if (uid == Process.myUid()) return result; boolean hideBssidAndSsid = true; //这里把MacAddress设成了一个默认值,这个值的内容,就是02:00:00:00:00:00 result.setMacAddress(WifiInfo.DEFAULT_MAC_ADDRESS); IPackageManager packageManager = AppGlobals.getPackageManager(); try { if (packageManager.checkUidPermission(Manifest.permission.LOCAL_MAC_ADDRESS, uid) == PackageManager.PERMISSION_GRANTED) { //这里又设置了MacAddress,原因就在这里, //第三方应用过不了这个权限检查,代码走不到这里,所以拿到的是02:00:00:00:00:00 result.setMacAddress(mWifiInfo.getMacAddress()); } final WifiConfiguration currentWifiConfiguration = getCurrentWifiConfiguration(); if (mWifiPermissionsUtil.canAccessFullConnectionInfo( currentWifiConfiguration, callingPackage, uid, Build.VERSION_CODES.O)) { hideBssidAndSsid = false; } } catch (RemoteException e) { Log.e(TAG, "Error checking receiver permission", e); } catch (SecurityException e) { Log.e(TAG, "Security exception checking receiver permission", e); } if (hideBssidAndSsid) { result.setBSSID(WifiInfo.DEFAULT_MAC_ADDRESS); result.setSSID(WifiSsid.createFromHex(null)); } return result; }

看下这个WifiInfo.DEFAULT_MAC_ADDRESS是什么:
frameworks/base/wifi/java/android/net/wifi/WifiInfo.java

/** * Default MAC address reported to a client that does not have the * android.permission.LOCAL_MAC_ADDRESS permission. * * @hide */ public static final String DEFAULT_MAC_ADDRESS = "02:00:00:00:00:00";

源码的注释非常清晰,你没有声明"android.permission.LOCAL_MAC_ADDRESS"这个权限,不按规矩来,那就给个DEFAULT_MAC_ADDRESS 忽悠一下吧!

既然是没权限,那简单,加上就行了。
然而,我在测试应用的AndroidManifest.xml加了权限,再测试,还是不行,这就尴尬了。

继续分析,看下这个权限是个啥东西,为啥加了还不行?
frameworks/base/core/res/AndroidManifest.xml

这个权限的protectionLevel是"signature|privileged",这个目的就很明确了,就是不让第三方应用拿MAC地址,除非你用系统签名。

如果客户说,我同样的代码,在A平台可以,为啥B平台就不行了,都是你们的产品啊!
那就改吧,把权限检查去掉,就OK了。

--- a/code/frameworks/opt/net/wifi/service/java/com/android/server/wifi/WifiStateMachine.java +++ b/code/frameworks/opt/net/wifi/service/java/com/android/server/wifi/WifiStateMachine.java @@ -1897,10 +1897,10 @@ public class WifiStateMachine extends StateMachine implements WifiNative.WifiRss IPackageManager packageManager = AppGlobals.getPackageManager(); try { - if (packageManager.checkUidPermission(Manifest.permission.LOCAL_MAC_ADDRESS, - uid) == PackageManager.PERMISSION_GRANTED) { +// if (packageManager.checkUidPermission(Manifest.permission.LOCAL_MAC_ADDRESS, +// uid) == PackageManager.PERMISSION_GRANTED) { result.setMacAddress(mWifiInfo.getMacAddress()); - } +// } final WifiConfiguration currentWifiConfiguration = getCurrentWifiConfiguration(); if (mWifiPermissionsUtil.canAccessFullConnectionInfo( currentWifiConfiguration,

再看看Android5.1为啥没问题。调用流程都是一样的,不多说,直接看关键代码。
frameworks/opt/net/wifi/service/java/com/android/server/wifi/WifiStateMachine.java

/** * Get status information for the current connection, if any. * @return a {@link WifiInfo} object containing information about the current connection * */ public WifiInfo syncRequestConnectionInfo() { return mWifiInfo; }

没有权限检查,直接把mWifiInfo丢回去,你再getMacAddress就OK了。

需要注意的是,如果WIFI没有打开过,可能拿不到MAC地址(取决于wifi模组),就是说要Supplicant跑起来,MAC才会传上来,这里就不深究了。
frameworks/opt/net/wifi/service/java/com/android/server/wifi/WifiStateMachine.java

@Override public boolean processMessage(Message message) { logStateAndMessage(message, getClass().getSimpleName()); switch(message.what) { case WifiMonitor.SUP_CONNECTION_EVENT: if (DBG) log("Supplicant connection established"); setWifiState(WIFI_STATE_ENABLED); //... ...省略部分代码 mWifiInfo.setMacAddress(mWifiNative.getMacAddress()); 2.BT MAC地址

应用代码:

public String getBlueToothMac() { String btMac = ""; try { BluetoothManager manager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE); btMac = manager.getAdapter().getAddress(); Log.d(TAG, "btMac: " + btMac); } catch (Exception e) { e.printStackTrace(); } return btMac; }

同Wifi一样,这个代码在Android8.1上运行,也是拿到固定地址02:00:00:00:00:00。
下面分析源码。
frameworks/base/core/java/android/bluetooth/BluetoothManager.java

/** * Get the default BLUETOOTH Adapter for this device. * * @return the default BLUETOOTH Adapter */ public BluetoothAdapter getAdapter() { return mAdapter;//返回BluetoothAdapter 实例 }

frameworks/base/core/java/android/bluetooth/BluetoothAdapter.java

/** * Returns the hardware address of the local Bluetooth adapter. *

For example, "00:11:22:AA:BB:CC". * * @return Bluetooth hardware address as string */ @RequiresPermission(Manifest.permission.BLUETOOTH) //需要“android.permission.BLUETOOTH”权限 public String getAddress() { try { return mManagerService.getAddress(); //调服务端的方法 } catch (RemoteException e) {Log.e(TAG, "", e);} return null; }

frameworks/base/services/core/java/com/android/server/BluetoothManagerService.java

public String getAddress() { //定义:private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH; mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); if ((Binder.getCallingUid() != Process.SYSTEM_UID) && (!checkIfCallerIsForegroundUser())) { Slog.w(TAG,"getAddress(): not allowed for non-active and non system user"); return null; } //这个权限检查跟wifi一样,如果没有“android.permission.LOCAL_MAC_ADDRESS”,就返回默认地址 if (mContext.checkCallingOrSelfPermission(Manifest.permission.LOCAL_MAC_ADDRESS) != PackageManager.PERMISSION_GRANTED) { return BluetoothAdapter.DEFAULT_MAC_ADDRESS; } try { mBluetoothLock.readLock().lock(); if (mBluetooth != null) return mBluetooth.getAddress(); } catch (RemoteException e) { Slog.e(TAG, "getAddress(): Unable to retrieve address remotely. Returning cached address", e); } finally { mBluetoothLock.readLock().unlock(); } // mAddress is accessed from outside. // It is alright without a lock. Here, bluetooth is off, no other thread is // changing mAddress return mAddress; }

跟wifi一个套路,跟着代码流程走一遍就行了,

3.Ethernet MAC地址

Android没有提供获取以太网MAC地址的API,但有一个API可以间接实现。

public String getEthernetMac() { String ethMac = ""; ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo info = cm.getNetworkInfo(ConnectivityManager.TYPE_ETHERNET); if (info != null) { ethMac = info.getExtraInfo();//这个ExtraInfo就是以太网的mac地址 Log.d(TAG, "ethernet mac = " + ethMac); } else { Log.e(TAG, "info is null !"); } return ethMac; }

为什么“getExtraInfo”就能拿到以太网的MAC地址?我们继续往下看。
frameworks/base/core/java/android/net/NetworkInfo.java

/** * Report the extra information about the network state, if any was * provided by the lower networking layers. * @return the extra information, or null if not available */ public String getExtraInfo() { synchronized (this) { return mExtraInfo; } } /** * Set the extraInfo field. * @param extraInfo an optional {@code String} providing addditional network state * information passed up from the lower networking layers. * @hide */ public void setExtraInfo(String extraInfo) { synchronized (this) { this.mExtraInfo = extraInfo; } }

这个“getExtraInfo”的本意是“Report the extra information about the network state”,具体是什么信息,取决于底层。
在这里插入图片描述
setExtraInfo添加了哪些附加信息?
如上图,
Ethernet是mHwAddr,
Wifi是mWifiInfo.getSSID(),
telephony是mApnSetting.apn
所以,对于以太网而言,getExtraInfo就能取出MAC地址。

4.修改应用

如果不改Android源码,而是由应用端来解决这个问题,有没有办法?
直接读节点,也是可行的。
wifi的节点:/sys/class/net/wlan0/address
ethernet的节点:/sys/class/net/eth0/address
Bt没有找到节点。
写一下获取wifi mac地址的代码,ethernet类似。

public String getWifiMacFromNode() { String wifiMac = ""; RandomAccessFile f = null; try { f = new RandomAccessFile("/sys/class/net/wlan0/address", "r"); f.seek(0); wifiMac = f.readLine().trim(); f.close(); Log.d(TAG, "getWifiMacFromNode "+wifiMac); return wifiMac; } catch (FileNotFoundException e) { e.printStackTrace(); return wifiMac; } catch (IOException e) { e.printStackTrace(); return wifiMac; } finally { if (f != null) { try { f.close(); f = null; } catch (IOException e) { e.printStackTrace(); } } } }

其他方法,反射调用Android的API,同时绕过权限检查,应该也是可行的。


作者:zhangchao2280



bt mac Android

需要 登录 后方可回复, 如果你还没有账号请 注册新账号