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

  • Post author:
  • Post category:其他




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

    <!-- @SystemApi Allows applications to read the local WiFi and Bluetooth MAC address.
        @hide -->
    <permission android:name="android.permission.LOCAL_MAC_ADDRESS"
                android:protectionLevel="signature|privileged" />

这个权限的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.
     * <p>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,同时绕过权限检查,应该也是可行的。



版权声明:本文为zc37093原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。