Lab 4: Location Spoofing
Prerequisites: Lab 2 (First Injection) complete, Chapter 8 (Location Spoofing) read.
Estimated time: 30 minutes.
Target:
materials/targets/target-kyc-basic.apk(packagecom.poc.biometric)
The target application has a LocationActivity that performs a geofence check. The device must appear to be near Times Square, New York City (40.7580, -73.9855) for the location verification step to pass. The app also calls isFromMockProvider() to detect GPS spoofing tools. You will assess both layers: simulate the required coordinates and test mock-detection handling (authorized practice target only).
This lab exercises the LocationInterceptor subsystem. By the end, you will have extracted geofence coordinates from the decoded APK, built a location config, and validated geofence behavior with mock-detection handling confirmed in logcat.
Step 1: Recon — Find the Geofence Coordinates
Section titled “Step 1: Recon — Find the Geofence Coordinates”If you completed Lab 1, you already have the decoded APK. If not, decode it now:
cd /Users/josejames/Documents/android-red-teamapktool d materials/targets/target-kyc-basic.apk -o decoded-kyc/Search the decoded smali for geofence coordinates. The app will have hardcoded latitude and longitude values somewhere in its location verification logic:
grep -rn "latitude\|longitude\|LatLng\|geofence" decoded-kyc/smali*/Look for float or double constants near known coordinate ranges. For US-based targets, latitude values near 40.x and longitude values near -73.x indicate the New York area:
grep -rn "const.*40\.\|const.*73\." decoded-kyc/smali*/Also check string resources:
grep -rn "latitude\|longitude" decoded-kyc/res/values/strings.xmlYou should find references to the Times Square coordinates: latitude 40.7580, longitude -73.9855. Record these — they are the center of the geofence you need to land inside.
Also confirm the mock detection surface:
grep -rn "isFromMockProvider\|isMock" decoded-kyc/smali*/This should return at least one hit in the LocationActivity. The app checks whether the delivered location came from a mock provider and rejects it if so. The patch-tool handles this at the smali level.
Step 2: Build the Location Config
Section titled “Step 2: Build the Location Config”Create a JSON config file with the extracted geofence coordinates:
cat > /tmp/geofence_config.json << 'EOF'{ "latitude": 40.7580, "longitude": -73.9855, "altitude": 5.0, "accuracy": 8.0, "speed": 0.0, "bearing": 0.0}EOFField rationale:
| Field | Value | Why |
|---|---|---|
latitude | 40.7580 | Center of the target geofence (Times Square) |
longitude | -73.9855 | Center of the target geofence (Times Square) |
altitude | 5.0 | Reasonable street-level altitude in meters |
accuracy | 8.0 | Tight accuracy — typical urban GPS fix (jittered +/-2m per delivery) |
speed | 0.0 | Stationary — person standing still for verification |
bearing | 0.0 | Irrelevant when stationary |
Step 3: Patch the APK
Section titled “Step 3: Patch the APK”Patch the target APK. Watch the output for location-related hook confirmations:
cd /Users/josejames/Documents/android-red-teamjava -jar patch-tool.jar materials/targets/target-kyc-basic.apk \ --out patched-location.apk \ --work-dir ./work-location 2>&1 | tee patch_location_output.txtIn the patch output, verify these entries appear:
onLocationResult— confirms the FusedLocationProvider callback hook was injectedisFromMockProvider— confirms the mock detection call site was patched to returnfalse
If you see [!] Not found for either of these, re-check that you are patching the correct APK. The materials/targets/target-kyc-basic.apk should have both surfaces.
Step 4: Install and Grant Permissions
Section titled “Step 4: Install and Grant Permissions”Install the patched APK and grant all required permissions up front so no permission dialogs interrupt the flow:
adb uninstall com.poc.biometric 2>/dev/nulladb install -r patched-location.apk
adb shell pm grant com.poc.biometric android.permission.CAMERAadb shell pm grant com.poc.biometric android.permission.ACCESS_FINE_LOCATIONadb shell pm grant com.poc.biometric android.permission.ACCESS_COARSE_LOCATIONadb shell pm grant com.poc.biometric android.permission.READ_EXTERNAL_STORAGEadb shell pm grant com.poc.biometric android.permission.WRITE_EXTERNAL_STORAGEadb shell appops set com.poc.biometric MANAGE_EXTERNAL_STORAGE allowThe ACCESS_FINE_LOCATION grant is critical for this lab. Without it, the app cannot request GPS updates at all, and the location hook will never fire.
Step 5: Push the Location Config
Section titled “Step 5: Push the Location Config”Create the payload directory on the device and push your config:
adb shell mkdir -p /sdcard/poc_location/adb push /tmp/geofence_config.json /sdcard/poc_location/config.jsonThe LocationInterceptor auto-enables when it finds a JSON file in /sdcard/poc_location/. No app restart is required if the app is already running — the config hot-reloads every 2 seconds.
Step 6: Launch and Observe
Section titled “Step 6: Launch and Observe”Start logcat monitoring in one terminal, then launch the app in another:
Terminal 1 — Monitor:
adb logcat -cadb logcat -s LocationInterceptorTerminal 2 — Launch:
adb shell am start -n com.poc.biometric/com.poc.biometric.ui.LauncherActivityNavigate to the location verification step in the app. Once the app queries location, you should see output in Terminal 1 confirming injection is active.
Step 7: Verify via Logcat
Section titled “Step 7: Verify via Logcat”Watch the logcat output for these key events:
D LocationInterceptor: Auto-enabled — config found at /sdcard/poc_location/config.jsonD LocationInterceptor: buildFakeLocation lat=40.7580 lng=-73.9855 alt=5.0 acc=8.0D LocationInterceptor: LOCATION_DELIVERED lat=40.758002 lng=-73.985498 acc=9.2D LocationInterceptor: LOCATION_DELIVERED lat=40.757998 lng=-73.985503 acc=7.8What to look for:
| Log Entry | Meaning |
|---|---|
Auto-enabled | LocationInterceptor found your config and armed itself |
buildFakeLocation | A fake Location object was constructed from your config |
LOCATION_DELIVERED | The fake Location was delivered to the app’s callback |
Notice the slight variation in coordinates between deliveries (40.758002 vs 40.757998). That is the accuracy jitter simulating realistic GPS drift. The app’s geofence check passes because all delivered coordinates fall within the acceptable radius of Times Square.
The mock detection bypass is silent — there is no separate log entry. The isFromMockProvider() call in the app’s code has been rewritten at the smali level to return false. It never executes the real check.
Step 8: Capture Evidence
Section titled “Step 8: Capture Evidence”Take a screenshot of the location verification passing:
adb exec-out screencap -p > geofence_pass.pngDump the delivery log:
adb logcat -d -s LocationInterceptor > location_log.txtSave a copy of your config:
cp /tmp/geofence_config.json ./geofence_config.jsonUnderstanding the two control layers
Section titled “Understanding the two control layers”This lab demonstrates assessment across two layers that many naive GPS tools never clear:
Layer 1: Location Injection. The LocationInterceptor hooks every path Android uses to deliver GPS data — onLocationResult, onLocationChanged, getLastLocation, getCurrentLocation. When the app asks “where is this phone?”, it receives your coordinates. The fake Location object is constructed fresh on every delivery with current timestamps, realistic accuracy jitter, and proper provider metadata. It is indistinguishable from a real GPS fix at the API level.
Layer 2: Mock Detection Evasion. Standard GPS spoofing apps set a mock location provider through Developer Options. Android marks these locations with isFromMockProvider() = true, which apps can check. The patch-tool takes a different approach: it rewrites the app’s own bytecode so that every call to isFromMockProvider() and isMock() returns false. The app cannot detect mocking because the detection code itself has been modified. This is fundamentally different from system-level spoofing — the modification is inside the target APK, invisible to the app’s runtime logic.
Together, these two layers mean the app receives coordinates you control and has no mechanism to determine they are synthetic.
Self-Check Script
Section titled “Self-Check Script”Run this script to verify your lab is complete:
#!/bin/bashecho "=== Lab 4: Location Spoofing — Self-Check ==="PASS=0FAIL=0
# Check config file existsif [ -f geofence_config.json ]; then echo "[PASS] geofence_config.json exists" ((PASS++))else echo "[FAIL] geofence_config.json not found" ((FAIL++))fi
# Check config has correct coordinatesif grep -q "40.7580" geofence_config.json 2>/dev/null && \ grep -q "\-73.9855" geofence_config.json 2>/dev/null; then echo "[PASS] Config contains Times Square coordinates" ((PASS++))else echo "[FAIL] Config missing expected coordinates (40.7580, -73.9855)" ((FAIL++))fi
# Check screenshot existsif [ -f geofence_pass.png ]; then echo "[PASS] geofence_pass.png exists" ((PASS++))else echo "[FAIL] geofence_pass.png not found" ((FAIL++))fi
# Check delivery log exists and has location eventsif [ -f location_log.txt ]; then LOC_COUNT=$(grep -c "LOCATION_DELIVERED" location_log.txt 2>/dev/null || echo 0) if [ "$LOC_COUNT" -gt 0 ]; then echo "[PASS] location_log.txt has $LOC_COUNT LOCATION_DELIVERED events" ((PASS++)) else echo "[FAIL] location_log.txt exists but contains no LOCATION_DELIVERED events" ((FAIL++)) fielse echo "[FAIL] location_log.txt not found" ((FAIL++))fi
# Check for auto-enable confirmationif grep -q "Auto-enabled\|buildFakeLocation" location_log.txt 2>/dev/null; then echo "[PASS] LocationInterceptor auto-enabled confirmed" ((PASS++))else echo "[FAIL] No auto-enable confirmation in location_log.txt" ((FAIL++))fi
echo ""echo "Results: $PASS passed, $FAIL failed out of $((PASS + FAIL)) checks"[ "$FAIL" -eq 0 ] && echo "Lab 4 COMPLETE." || echo "Lab 4 INCOMPLETE — review failed checks."Troubleshooting
Section titled “Troubleshooting”No LOCATION_DELIVERED events in logcat. The app may not have reached the location verification step yet. Navigate through the app UI until you reach the screen that checks your location. The hook only fires when the app actually requests a location update.
Coordinates delivered but geofence still fails. Double-check the coordinates you extracted during recon. If the geofence has a tight radius (e.g., 50 meters), even small errors in the latitude or longitude will cause rejection. Ensure your config matches the extracted values exactly.
“Mock location detected” message despite patching. Verify that isFromMockProvider appeared in the patch output. If it shows [!] Not found, the app may use a non-standard mock detection method (proprietary SDK). Check the recon for third-party anti-spoofing libraries.
Permission denied when pushing config. Run adb shell appops set com.poc.biometric MANAGE_EXTERNAL_STORAGE allow and ensure the /sdcard/poc_location/ directory exists.
Deliverables
Section titled “Deliverables”| File | Description |
|---|---|
geofence_config.json | Location config with extracted geofence coordinates |
geofence_pass.png | Screenshot of location verification passing |
location_log.txt | Logcat output showing LOCATION_DELIVERED events |
Success Criteria
Section titled “Success Criteria”- Geofence coordinates extracted from decoded APK via grep
- Location config JSON built with correct latitude, longitude, altitude, and accuracy
- Patch output confirms
onLocationResultandisFromMockProviderhooks -
ACCESS_FINE_LOCATIONpermission granted before launch - LocationInterceptor auto-enabled (confirmed in logcat)
- At least one
LOCATION_DELIVEREDevent in logcat with Times Square coordinates - Geofence check passes in the app (screenshot captured)
- All three deliverables saved