引言
前段時間看到朋友圈有人在短時間內發了幾條狀態,定位都在不同國家的首都。問了一下,才知道用了一款能夠模擬位置的軟件。最近學習Xposed框架,就試著利用Xposed框架開發了一款能夠模擬安卓手機位置的應用模塊。
開發環境
- 測試機:Android 4.4
- Xposed框架
- AndroidStudio
實現原理
1.Android手機定位原理
手機常用的定位方式有:
- 衛星定位(GPS,北斗,伽利略,Glonass)
- 移動基站定位
- WiFi輔助定位
- AGPS定位
* 衛星定位
GPS(Global Positioning System)即全球定位系統,是由美國建立的一個衛星導航定位系統,利用該系統,用戶可以在全球范圍內實現全天候、連續、實時的三維導航定位和測速;另外,利用該系統,用戶還能夠進行高精度的時間傳遞和高精度的精密定位。
* 基站定位
移動電話測量不同基站的下行導頻信號,得到不同基站下行導頻的TOA(到達時刻)或 TDOA(到達時間差),根據該測量結果并結合基站的坐標,一般采用三角公式估計算法,就能夠計算出移動電話的位置。實際的位置估計算法需要考慮多基站(3個或3個以上)定位的情況,因此算法要復雜很多。一般而言,移動臺測量的基站數目越多,測量精度越高,定位性能改善越明顯。
* WiFi定位
- 每一個無線AP(路由器)都有一個全球唯一的MAC地址,并且一般來說無線AP在一段時間內不會移動;
- 設備在開啟Wi-Fi的情況下,無線路由器默認都會進行SSID廣播(除非用戶手動配置關閉該功能),在廣播幀包含了該路由器的MAC地址;
- 采集裝置可以通過接收周圍AP發送的廣播信息獲取周圍AP的MAC信息和信號強度信息,將這些信息上傳到服務器,經過服務器的計算,保存為“MAC-經緯度”的映射,當采集的信息足夠多時候就在服務器上建立了一張巨大的WiFi信息網絡;
- 當一個設備處在這樣的網絡中時,可以將收集到的這些能夠標示AP的數據發送到位置服務器,服務器檢索出每一個AP的地理位置,并結合每個信號的強弱程度,計算出設備的地理位置并返回到用戶設備,其計算方式和基站定位位置計算方式相似,也是利用三點定位或多點定位技術;
- 位置服務商要不斷更新、補充自己的數據庫,以保證數據的準確性。當某些WiFi信息不在數據庫中時,可以根據附近其他的WiFi位置信息推斷出未知WiFi的位置信息,并上傳服務器。
* AGPS定位
AGPS(AssistedGPS:輔助全球衛星定位系統)是結合GSM/GPRS與傳統衛星定位,利用基地臺代送輔助衛星信息,以縮減GPS芯片獲取衛星信號的延遲時間,受遮蓋的室內也能借基地臺訊號彌補,減輕GPS芯片對衛星的依賴度。AGPS利用手機基站的信號,輔以連接遠程定位服務器的方式下載衛星星歷 (英語:Almanac Data),再配合傳統的GPS衛星接受器,讓定位的速度更快。是一種結合網絡基站信息和GPS信息對移動臺進行定位的技術,既利用全球衛星定位系統GPS,又利用移動基站,解決了GPS覆蓋的問題,可以在2代的G、C網絡和3G網絡中使用。
1.偽裝定位思路
在了解到上述手機定位原理后,結合平時對手機的使用我們可以得知手機定位最常用的幾種方式分別是:
- WiFi
- GPS定位
- 基站定位
Xposed的便利之處就是提供方法使得我們可以改變系統函數和應用中的函數執行前和執行后的結果。所以設想是否可以利用XPosed框架提供的功能編寫一個Hook模塊,勾取系統調用中和定位相關的函數并篡改返回值呢?
2.相關函數
經過查閱資料和閱讀安卓源碼,粗略找到以下幾個類和相關的方法和定位有關,并對其進行Hook操作。下面是我進行Hook的類及其中的方法名:
- android.telephony.TelephonyManager
- getCellLocation
- getPhoneCount
- getNeighboringCellInfo
- getAllCellInfo
- android.telephony.PhoneStateListener
- onCellLocationChanged
- onCellInfoChanged
- android.net.wifi.WifiManager
- getScanResults
- getWifiState
- isWifiEnabled
- android.net.wifi.WifiInfo
- getMacAddress
- getSSID
- getBSSID
- android.net.NetworkInfo
- getTypeName
- isConnectedOrConnecting
- isConnected
- isAvailable
- android.telephony.CellInfo
- isRegistered
- LocationManager.class
- getLastLocation
- getLastKnownLocation
- getProviders
- getBestProvider
- addGpsStatusListener
- addNmeaListener
- android.location.LocationManager
- getGpsStatus
3.解釋
由于上述方法太多,這里只解釋基本的思想。這些方法的作用及參數和返回值都能在Android開發手冊中找到,逐條hook并修改返回值即可。我們要做的其實就是利用Hook手段,讓手機認為gps是目前最好的位置提供器,并修改其返回值為我們想要的位置,從而達到偽裝位置的目的。但是為什么上面列出如此眾多的方法需要我們Hook呢?這是因為手機中的定位是一連串比較復雜的過程,是一套各參數匹配的過程。任何一個相關函數的返回值和最終我們填入的結果不吻合,都可能導致偽裝定位的失敗。所以我們要做的就是找到并Hook定位流程相關的方法并攔截修改返回值。
附上:Android開發手冊
4.效果截圖
上圖是我將核心Hook模塊實現后利用百度地圖做了一個簡單的欺騙位置小軟件。
5.核心Hook模塊代碼
package com.example.administrator.hook;
import android.location.Criteria;
import android.location.GpsStatus;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Build;
import android.os.SystemClock;
import android.telephony.CellIdentityCdma;
import android.telephony.CellIdentityGsm;
import android.telephony.CellIdentityLte;
import android.telephony.CellIdentityWcdma;
import android.telephony.CellInfoCdma;
import android.telephony.CellInfoGsm;
import android.telephony.CellInfoLte;
import android.telephony.CellInfoWcdma;
import android.telephony.CellLocation;
import android.telephony.gsm.GsmCellLocation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedHelpers;
/**
* Created by CaptainXero on 2016/8/31 0031.
*/
public class HookUtils {
public static void HookAndChange(ClassLoader classLoader, final double latitude, final double longtitude, final int lac, final int cid) {
XposedHelpers.findAndHookMethod("android.telephony.TelephonyManager", classLoader,
"getCellLocation", new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
GsmCellLocation gsmCellLocation = new GsmCellLocation();
gsmCellLocation.setLacAndCid(lac, cid);
param.setResult(gsmCellLocation);
}
});
XposedHelpers.findAndHookMethod("android.telephony.PhoneStateListener", classLoader,
"onCellLocationChanged", CellLocation.class, new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
GsmCellLocation gsmCellLocation = new GsmCellLocation();
gsmCellLocation.setLacAndCid(lac, cid);
param.setResult(gsmCellLocation);
}
});
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP_MR1) {
XposedHelpers.findAndHookMethod("android.telephony.TelephonyManager", classLoader,
"getPhoneCount", new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
param.setResult(1);
}
});
}
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
XposedHelpers.findAndHookMethod("android.telephony.TelephonyManager", classLoader,
"getNeighboringCellInfo", new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
param.setResult(new ArrayList<>());
}
});
}
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN) {
XposedHelpers.findAndHookMethod("android.telephony.TelephonyManager", classLoader,
"getAllCellInfo", new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
param.setResult(getCell(460, 0, lac, cid, 0, 0));
}
});
XposedHelpers.findAndHookMethod("android.telephony.PhoneStateListener", classLoader,
"onCellInfoChanged", List.class, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
param.setResult(getCell(460, 0, lac, cid, 0,0));
}
});
}
XposedHelpers.findAndHookMethod("android.net.wifi.WifiManager", classLoader, "getScanResults", new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
param.setResult(new ArrayList<>());
}
});
XposedHelpers.findAndHookMethod("android.net.wifi.WifiManager", classLoader, "getWifiState", new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
param.setResult(1);
}
});
XposedHelpers.findAndHookMethod("android.net.wifi.WifiManager", classLoader, "isWifiEnabled", new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
param.setResult(true);
}
});
XposedHelpers.findAndHookMethod("android.net.wifi.WifiInfo", classLoader, "getMacAddress", new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
param.setResult("00-00-00-00-00-00-00-00");
}
});
XposedHelpers.findAndHookMethod("android.net.wifi.WifiInfo", classLoader, "getSSID", new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
param.setResult("null");
}
});
XposedHelpers.findAndHookMethod("android.net.wifi.WifiInfo", classLoader, "getBSSID", new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
param.setResult("00-00-00-00-00-00-00-00");
}
});
XposedHelpers.findAndHookMethod("android.net.NetworkInfo", classLoader,
"getTypeName", new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
param.setResult("WIFI");
}
});
XposedHelpers.findAndHookMethod("android.net.NetworkInfo", classLoader,
"isConnectedOrConnecting", new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
param.setResult(true);
}
});
XposedHelpers.findAndHookMethod("android.net.NetworkInfo", classLoader,
"isConnected", new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
param.setResult(true);
}
});
XposedHelpers.findAndHookMethod("android.net.NetworkInfo", classLoader,
"isAvailable", new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
param.setResult(true);
}
});
XposedHelpers.findAndHookMethod("android.telephony.CellInfo", classLoader,
"isRegistered", new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
param.setResult(true);
}
});
XposedHelpers.findAndHookMethod(LocationManager.class, "getLastLocation", new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
Location l = new Location(LocationManager.GPS_PROVIDER);
l.setLatitude(latitude);
l.setLongitude(longtitude);
l.setAccuracy(100f);
l.setTime(System.currentTimeMillis());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
l.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());
}
param.setResult(l);
}
});
XposedHelpers.findAndHookMethod(LocationManager.class, "getLastKnownLocation", String.class, new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
Location l = new Location(LocationManager.GPS_PROVIDER);
l.setLatitude(latitude);
l.setLongitude(longtitude);
l.setAccuracy(100f);
l.setTime(System.currentTimeMillis());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
l.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());
}
param.setResult(l);
}
});
XposedBridge.hookAllMethods(LocationManager.class, "getProviders", new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
ArrayList<String> arrayList = new ArrayList<>();
arrayList.add("gps");
param.setResult(arrayList);
}
});
XposedHelpers.findAndHookMethod(LocationManager.class, "getBestProvider", Criteria.class, Boolean.TYPE, new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
param.setResult("gps");
}
});
XposedHelpers.findAndHookMethod(LocationManager.class, "addGpsStatusListener", GpsStatus.Listener.class, new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
if (param.args[0] != null) {
XposedHelpers.callMethod(param.args[0], "onGpsStatusChanged", 1);
XposedHelpers.callMethod(param.args[0], "onGpsStatusChanged", 3);
}
}
});
XposedHelpers.findAndHookMethod(LocationManager.class, "addNmeaListener", GpsStatus.NmeaListener.class, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
param.setResult(false);
}
});
XposedHelpers.findAndHookMethod("android.location.LocationManager", classLoader,
"getGpsStatus", GpsStatus.class, new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
GpsStatus gss = (GpsStatus) param.getResult();
if (gss == null)
return;
Class<?> clazz = GpsStatus.class;
Method m = null;
for (Method method : clazz.getDeclaredMethods()) {
if (method.getName().equals("setStatus")) {
if (method.getParameterTypes().length > 1) {
m = method;
break;
}
}
}
if (m == null)
return;
//access the private setStatus function of GpsStatus
m.setAccessible(true);
//make the apps belive GPS works fine now
int svCount = 5;
int[] prns = {1, 2, 3, 4, 5};
float[] snrs = {0, 0, 0, 0, 0};
float[] elevations = {0, 0, 0, 0, 0};
float[] azimuths = {0, 0, 0, 0, 0};
int ephemerisMask = 0x1f;
int almanacMask = 0x1f;
//5 satellites are fixed
int usedInFixMask = 0x1f;
XposedHelpers.callMethod(gss, "setStatus", svCount, prns, snrs, elevations, azimuths, ephemerisMask, almanacMask, usedInFixMask);
param.args[0] = gss;
param.setResult(gss);
try {
m.invoke(gss, svCount, prns, snrs, elevations, azimuths, ephemerisMask, almanacMask, usedInFixMask);
param.setResult(gss);
} catch (Exception e) {
XposedBridge.log(e);
}
}
});
for (Method method : LocationManager.class.getDeclaredMethods()) {
if (method.getName().equals("requestLocationUpdates")
&& !Modifier.isAbstract(method.getModifiers())
&& Modifier.isPublic(method.getModifiers())) {
XposedBridge.hookMethod(method, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
if (param.args.length >= 4 && (param.args[3] instanceof LocationListener)) {
LocationListener ll = (LocationListener) param.args[3];
Class<?> clazz = LocationListener.class;
Method m = null;
for (Method method : clazz.getDeclaredMethods()) {
if (method.getName().equals("onLocationChanged") && !Modifier.isAbstract(method.getModifiers())) {
m = method;
break;
}
}
Location l = new Location(LocationManager.GPS_PROVIDER);
l.setLatitude(latitude);
l.setLongitude(longtitude);
l.setAccuracy(10.00f);
l.setTime(System.currentTimeMillis());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
l.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());
}
XposedHelpers.callMethod(ll, "onLocationChanged", l);
try {
if (m != null) {
m.invoke(ll, l);
}
} catch (Exception e) {
XposedBridge.log(e);
}
}
}
});
}
if (method.getName().equals("requestSingleUpdate ")
&& !Modifier.isAbstract(method.getModifiers())
&& Modifier.isPublic(method.getModifiers())) {
XposedBridge.hookMethod(method, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
if (param.args.length >= 3 && (param.args[1] instanceof LocationListener)) {
LocationListener ll = (LocationListener) param.args[3];
Class<?> clazz = LocationListener.class;
Method m = null;
for (Method method : clazz.getDeclaredMethods()) {
if (method.getName().equals("onLocationChanged") && !Modifier.isAbstract(method.getModifiers())) {
m = method;
break;
}
}
try {
if (m != null) {
Location l = new Location(LocationManager.GPS_PROVIDER);
l.setLatitude(latitude);
l.setLongitude(longtitude);
l.setAccuracy(100f);
l.setTime(System.currentTimeMillis());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
l.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos());
}
m.invoke(ll, l);
}
} catch (Exception e) {
XposedBridge.log(e);
}
}
}
});
}
}
}
private static ArrayList getCell(int mcc, int mnc, int lac, int cid, int sid, int networkType) {
ArrayList arrayList = new ArrayList();
CellInfoGsm cellInfoGsm = (CellInfoGsm) XposedHelpers.newInstance(CellInfoGsm.class);
XposedHelpers.callMethod(cellInfoGsm, "setCellIdentity", XposedHelpers.newInstance(CellIdentityGsm.class, new Object[]{Integer.valueOf(mcc), Integer.valueOf(mnc), Integer.valueOf(
lac), Integer.valueOf(cid)}));
CellInfoCdma cellInfoCdma = (CellInfoCdma) XposedHelpers.newInstance(CellInfoCdma.class);
XposedHelpers.callMethod(cellInfoCdma, "setCellIdentity", XposedHelpers.newInstance(CellIdentityCdma.class, new Object[]{Integer.valueOf(lac), Integer.valueOf(sid), Integer.valueOf(cid), Integer.valueOf(0), Integer.valueOf(0)}));
CellInfoWcdma cellInfoWcdma = (CellInfoWcdma) XposedHelpers.newInstance(CellInfoWcdma.class);
XposedHelpers.callMethod(cellInfoWcdma, "setCellIdentity", XposedHelpers.newInstance(CellIdentityWcdma.class, new Object[]{Integer.valueOf(mcc), Integer.valueOf(mnc), Integer.valueOf(lac), Integer.valueOf(cid), Integer.valueOf(300)}));
CellInfoLte cellInfoLte = (CellInfoLte) XposedHelpers.newInstance(CellInfoLte.class);
XposedHelpers.callMethod(cellInfoLte, "setCellIdentity", XposedHelpers.newInstance(CellIdentityLte.class, new Object[]{Integer.valueOf(mcc), Integer.valueOf(mnc), Integer.valueOf(cid), Integer.valueOf(300), Integer.valueOf(lac)}));
if (networkType == 1 || networkType == 2) {
arrayList.add(cellInfoGsm);
} else if (networkType == 13) {
arrayList.add(cellInfoLte);
} else if (networkType == 4 || networkType == 5 || networkType == 6 || networkType == 7 || networkType == 12 || networkType == 14) {
arrayList.add(cellInfoCdma);
} else if (networkType == 3 || networkType == 8 || networkType == 9 || networkType == 10 || networkType == 15) {
arrayList.add(cellInfoWcdma);
}
return arrayList;
}
}