mirror of
https://github.com/xweiba/location-spoofer.git
synced 2026-09-22 06:40:44 +08:00
- iOS 虚拟定位工具,基于本地 HTTP 代理 MITM 方案 - MapKit 原生地图体验,支持搜索、收藏、实时定位 - 完整的设置引导流程(证书安装、WiFi 代理配置、环境验证) - 支持 iOS 15+,SwiftUI 构建
485 lines
12 KiB
Go
485 lines
12 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"compress/gzip"
|
|
"encoding/binary"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"math"
|
|
"regexp"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
wireVarint = 0
|
|
wireFixed64 = 1
|
|
wireLengthDelim = 2
|
|
wireFixed32 = 5
|
|
)
|
|
|
|
type wlocCoords struct {
|
|
Latitude float64
|
|
Longitude float64
|
|
Accuracy int
|
|
}
|
|
|
|
type patchStats struct {
|
|
WiFi int
|
|
Cell int
|
|
Locations int
|
|
Skipped int
|
|
}
|
|
|
|
type wireField struct {
|
|
num int
|
|
wireType int
|
|
value []byte
|
|
raw []byte
|
|
}
|
|
|
|
var macPattern = regexp.MustCompile(`^[0-9a-fA-F]{1,2}(:[0-9a-fA-F]{1,2}){5}$`)
|
|
|
|
func minInt(a, b int) int {
|
|
if a < b {
|
|
return a
|
|
}
|
|
return b
|
|
}
|
|
|
|
func maxInt(a, b int) int {
|
|
if a > b {
|
|
return a
|
|
}
|
|
return b
|
|
}
|
|
|
|
func cloneBytes(b []byte) []byte {
|
|
return append([]byte(nil), b...)
|
|
}
|
|
|
|
func readVarint(data []byte) (uint64, int, error) {
|
|
var v uint64
|
|
for i := 0; i < len(data); i++ {
|
|
if i >= 10 {
|
|
return 0, 0, errors.New("varint too long")
|
|
}
|
|
b := data[i]
|
|
v |= uint64(b&0x7f) << (7 * i)
|
|
if b&0x80 == 0 {
|
|
return v, i + 1, nil
|
|
}
|
|
}
|
|
return 0, 0, errors.New("truncated varint")
|
|
}
|
|
|
|
func writeVarint(v uint64) []byte {
|
|
var out []byte
|
|
for v >= 0x80 {
|
|
out = append(out, byte(v)|0x80)
|
|
v >>= 7
|
|
}
|
|
return append(out, byte(v))
|
|
}
|
|
|
|
func writeTag(num, wireType int) []byte {
|
|
return writeVarint(uint64(num<<3 | wireType))
|
|
}
|
|
|
|
func writeLengthDelimited(num int, value []byte) []byte {
|
|
var out []byte
|
|
out = append(out, writeTag(num, wireLengthDelim)...)
|
|
out = append(out, writeVarint(uint64(len(value)))...)
|
|
out = append(out, value...)
|
|
return out
|
|
}
|
|
|
|
func parseFields(data []byte) ([]wireField, error) {
|
|
var fields []wireField
|
|
idx := 0
|
|
for idx < len(data) {
|
|
start := idx
|
|
tag, n, err := readVarint(data[idx:])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
idx += n
|
|
num := int(tag >> 3)
|
|
wire := int(tag & 7)
|
|
if num == 0 {
|
|
return nil, errors.New("invalid protobuf field 0")
|
|
}
|
|
var value []byte
|
|
switch wire {
|
|
case wireVarint:
|
|
_, vn, err := readVarint(data[idx:])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
value = cloneBytes(data[idx : idx+vn])
|
|
idx += vn
|
|
case wireFixed64:
|
|
if idx+8 > len(data) {
|
|
return nil, errors.New("truncated fixed64")
|
|
}
|
|
value = cloneBytes(data[idx : idx+8])
|
|
idx += 8
|
|
case wireLengthDelim:
|
|
l, ln, err := readVarint(data[idx:])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
idx += ln
|
|
if l > uint64(len(data)-idx) {
|
|
return nil, errors.New("truncated length-delimited")
|
|
}
|
|
value = cloneBytes(data[idx : idx+int(l)])
|
|
idx += int(l)
|
|
case wireFixed32:
|
|
if idx+4 > len(data) {
|
|
return nil, errors.New("truncated fixed32")
|
|
}
|
|
value = cloneBytes(data[idx : idx+4])
|
|
idx += 4
|
|
default:
|
|
return nil, fmt.Errorf("unsupported wire type %d", wire)
|
|
}
|
|
fields = append(fields, wireField{
|
|
num: num,
|
|
wireType: wire,
|
|
value: value,
|
|
raw: cloneBytes(data[start:idx]),
|
|
})
|
|
}
|
|
return fields, nil
|
|
}
|
|
|
|
func patchLocation(loc []byte, c wlocCoords) ([]byte, bool, error) {
|
|
fields, err := parseFields(loc)
|
|
if err != nil {
|
|
return loc, false, err
|
|
}
|
|
hasLat, hasLon := false, false
|
|
for _, f := range fields {
|
|
if f.num == 1 && f.wireType == wireVarint {
|
|
hasLat = true
|
|
}
|
|
if f.num == 2 && f.wireType == wireVarint {
|
|
hasLon = true
|
|
}
|
|
}
|
|
if !hasLat || !hasLon {
|
|
return loc, false, nil
|
|
}
|
|
|
|
lat := int64(math.Round(c.Latitude * 1e8))
|
|
lon := int64(math.Round(c.Longitude * 1e8))
|
|
var out []byte
|
|
changed := false
|
|
for _, f := range fields {
|
|
switch {
|
|
case f.num == 1 && f.wireType == wireVarint:
|
|
raw := append(writeTag(1, wireVarint), writeVarint(uint64(lat))...)
|
|
if !bytes.Equal(raw, f.raw) {
|
|
changed = true
|
|
}
|
|
out = append(out, raw...)
|
|
case f.num == 2 && f.wireType == wireVarint:
|
|
raw := append(writeTag(2, wireVarint), writeVarint(uint64(lon))...)
|
|
if !bytes.Equal(raw, f.raw) {
|
|
changed = true
|
|
}
|
|
out = append(out, raw...)
|
|
case f.num == 3 && f.wireType == wireVarint:
|
|
raw := append(writeTag(3, wireVarint), writeVarint(uint64(c.Accuracy))...)
|
|
if !bytes.Equal(raw, f.raw) {
|
|
changed = true
|
|
}
|
|
out = append(out, raw...)
|
|
default:
|
|
out = append(out, f.raw...)
|
|
}
|
|
}
|
|
return out, changed, nil
|
|
}
|
|
|
|
func patchWifiDevice(device []byte, c wlocCoords, st *patchStats) ([]byte, bool, error) {
|
|
fields, err := parseFields(device)
|
|
if err != nil {
|
|
return device, false, err
|
|
}
|
|
hasMac := false
|
|
for _, f := range fields {
|
|
if f.num == 1 && f.wireType == wireLengthDelim && macPattern.Match(f.value) {
|
|
hasMac = true
|
|
}
|
|
}
|
|
if !hasMac {
|
|
return device, false, nil
|
|
}
|
|
|
|
var out []byte
|
|
changed := false
|
|
for _, f := range fields {
|
|
if f.num == 2 && f.wireType == wireLengthDelim {
|
|
newVal, subChanged, err := patchLocation(f.value, c)
|
|
if err != nil {
|
|
st.Skipped++
|
|
out = append(out, f.raw...)
|
|
continue
|
|
}
|
|
if subChanged {
|
|
changed = true
|
|
st.Locations++
|
|
}
|
|
out = append(out, writeLengthDelimited(2, newVal)...)
|
|
} else {
|
|
out = append(out, f.raw...)
|
|
}
|
|
}
|
|
if changed {
|
|
st.WiFi++
|
|
}
|
|
return out, changed, nil
|
|
}
|
|
|
|
func patchCellResponse(cell []byte, c wlocCoords, st *patchStats) ([]byte, bool, error) {
|
|
fields, err := parseFields(cell)
|
|
if err != nil {
|
|
return cell, false, err
|
|
}
|
|
|
|
var out []byte
|
|
changed := false
|
|
for _, f := range fields {
|
|
if f.num == 5 && f.wireType == wireLengthDelim {
|
|
newVal, subChanged, err := patchLocation(f.value, c)
|
|
if err != nil {
|
|
st.Skipped++
|
|
out = append(out, f.raw...)
|
|
continue
|
|
}
|
|
if subChanged {
|
|
changed = true
|
|
st.Locations++
|
|
}
|
|
out = append(out, writeLengthDelimited(5, newVal)...)
|
|
} else {
|
|
out = append(out, f.raw...)
|
|
}
|
|
}
|
|
if changed {
|
|
st.Cell++
|
|
}
|
|
return out, changed, nil
|
|
}
|
|
|
|
func patchWlocPayload(payload []byte, c wlocCoords, st *patchStats) ([]byte, bool, error) {
|
|
fields, err := parseFields(payload)
|
|
if err != nil {
|
|
return payload, false, err
|
|
}
|
|
|
|
var out []byte
|
|
changed := false
|
|
for _, f := range fields {
|
|
switch {
|
|
case f.num == 2 && f.wireType == wireLengthDelim:
|
|
newVal, subChanged, err := patchWifiDevice(f.value, c, st)
|
|
if err != nil {
|
|
st.Skipped++
|
|
out = append(out, f.raw...)
|
|
continue
|
|
}
|
|
if subChanged {
|
|
changed = true
|
|
}
|
|
out = append(out, writeLengthDelimited(2, newVal)...)
|
|
case (f.num == 22 || f.num == 24) && f.wireType == wireLengthDelim:
|
|
newVal, subChanged, err := patchCellResponse(f.value, c, st)
|
|
if err != nil {
|
|
st.Skipped++
|
|
out = append(out, f.raw...)
|
|
continue
|
|
}
|
|
if subChanged {
|
|
changed = true
|
|
}
|
|
out = append(out, writeLengthDelimited(f.num, newVal)...)
|
|
default:
|
|
out = append(out, f.raw...)
|
|
}
|
|
}
|
|
return out, changed, nil
|
|
}
|
|
|
|
func patchFrame(body []byte, offset int, c wlocCoords, st *patchStats) ([]byte, patchStats, error) {
|
|
if len(body) < offset+10 {
|
|
return nil, *st, fmt.Errorf("body too short: %d, base=%d", len(body), offset)
|
|
}
|
|
length := int(binary.BigEndian.Uint16(body[offset+8 : offset+10]))
|
|
if length <= 0 {
|
|
return nil, *st, errors.New("invalid empty frame length")
|
|
}
|
|
if offset+10+length > len(body) {
|
|
return nil, *st, fmt.Errorf("invalid frame length %d at %d for %d", length, offset, len(body))
|
|
}
|
|
|
|
prefix := cloneBytes(body[:offset+8])
|
|
payload := cloneBytes(body[offset+10 : offset+10+length])
|
|
suffix := cloneBytes(body[offset+10+length:])
|
|
before := *st
|
|
newPayload, changed, err := patchWlocPayload(payload, c, st)
|
|
if err != nil || !changed || (int(st.WiFi-before.WiFi)+int(st.Cell-before.Cell)+int(st.Locations-before.Locations)) <= 0 || bytes.Equal(newPayload, payload) {
|
|
*st = before
|
|
if err != nil {
|
|
return nil, *st, err
|
|
}
|
|
return nil, *st, errors.New("frame parsed but no patchable wloc payload")
|
|
}
|
|
if len(newPayload) > 65535 {
|
|
*st = before
|
|
return nil, *st, errors.New("patched payload too large")
|
|
}
|
|
|
|
var lenBytes [2]byte
|
|
binary.BigEndian.PutUint16(lenBytes[:], uint16(len(newPayload)))
|
|
out := append(prefix, lenBytes[:]...)
|
|
out = append(out, newPayload...)
|
|
out = append(out, suffix...)
|
|
return out, *st, nil
|
|
}
|
|
|
|
func patchWlocBody(body []byte, c wlocCoords) ([]byte, patchStats, error) {
|
|
var st patchStats
|
|
offsets := []int{0, 2, 4, 6, 8, 10, 12, 14, 16}
|
|
seen := map[int]bool{}
|
|
for _, o := range offsets {
|
|
seen[o] = true
|
|
}
|
|
limit := minInt(96, maxInt(0, len(body)-10))
|
|
for i := 0; i <= limit; i++ {
|
|
if !seen[i] {
|
|
offsets = append(offsets, i)
|
|
}
|
|
}
|
|
|
|
for _, offset := range offsets {
|
|
local := st
|
|
out, _, err := patchFrame(body, offset, c, &local)
|
|
if err == nil {
|
|
return out, local, nil
|
|
}
|
|
st = local
|
|
}
|
|
|
|
fallbackLimit := minInt(256, len(body))
|
|
for i := 0; i <= fallbackLimit; i++ {
|
|
local := patchStats{}
|
|
payload := body[i:]
|
|
newPayload, changed, err := patchWlocPayload(payload, c, &local)
|
|
if err == nil && changed && !bytes.Equal(newPayload, payload) {
|
|
out := append(cloneBytes(body[:i]), newPayload...)
|
|
return out, local, nil
|
|
}
|
|
}
|
|
return nil, st, errors.New("no patchable wloc payload found")
|
|
}
|
|
|
|
func maybeGunzip(body []byte) ([]byte, bool, error) {
|
|
if len(body) >= 2 && body[0] == 0x1f && body[1] == 0x8b {
|
|
zr, err := gzip.NewReader(bytes.NewReader(body))
|
|
if err != nil {
|
|
return nil, true, err
|
|
}
|
|
defer zr.Close()
|
|
out, err := io.ReadAll(zr)
|
|
return out, true, err
|
|
}
|
|
return body, false, nil
|
|
}
|
|
|
|
func patchResponseBody(body []byte, c wlocCoords) ([]byte, patchStats, error) {
|
|
decompressed, wasGzip, err := maybeGunzip(body)
|
|
if err != nil {
|
|
return nil, patchStats{}, err
|
|
}
|
|
patched, stats, err := patchWlocBody(decompressed, c)
|
|
if err != nil {
|
|
return nil, patchStats{}, err
|
|
}
|
|
_ = wasGzip
|
|
return patched, stats, nil
|
|
}
|
|
|
|
func makeTestWlocBody() []byte {
|
|
var loc []byte
|
|
loc = append(loc, writeTag(1, wireVarint)...)
|
|
loc = append(loc, writeVarint(100)...)
|
|
loc = append(loc, writeTag(2, wireVarint)...)
|
|
loc = append(loc, writeVarint(200)...)
|
|
loc = append(loc, writeTag(3, wireVarint)...)
|
|
loc = append(loc, writeVarint(25)...)
|
|
|
|
mac := []byte("aa:bb:cc:dd:ee:ff")
|
|
var device []byte
|
|
device = append(device, writeLengthDelimited(1, mac)...)
|
|
device = append(device, writeLengthDelimited(2, loc)...)
|
|
|
|
payload := writeLengthDelimited(2, device)
|
|
|
|
magic := []byte{0, 1, 0, 0, 0, 1, 0, 0}
|
|
var lenBytes [2]byte
|
|
binary.BigEndian.PutUint16(lenBytes[:], uint16(len(payload)))
|
|
var out []byte
|
|
out = append(out, magic...)
|
|
out = append(out, lenBytes[:]...)
|
|
out = append(out, payload...)
|
|
return out
|
|
}
|
|
|
|
func makeTestWlocRequest() []byte {
|
|
var out []byte
|
|
|
|
// 3 个 Wi-Fi AP(真实 wloc 请求格式)
|
|
type ap struct {
|
|
mac string
|
|
rssi int32
|
|
channel int32
|
|
}
|
|
aps := []ap{
|
|
{"aa:bb:cc:dd:ee:ff", -45, 6},
|
|
{"11:22:33:44:55:66", -62, 11},
|
|
{"77:88:99:00:11:22", -71, 1},
|
|
}
|
|
now := uint32(time.Now().Unix())
|
|
for _, a := range aps {
|
|
var device []byte
|
|
device = append(device, writeLengthDelimited(1, []byte(a.mac))...)
|
|
device = append(device, writeTag(4, wireVarint)...)
|
|
device = append(device, writeVarint(uint64(int64(a.rssi)))...)
|
|
device = append(device, writeTag(6, wireVarint)...)
|
|
device = append(device, writeVarint(uint64(a.channel))...)
|
|
device = append(device, writeTag(11, wireVarint)...)
|
|
device = append(device, writeVarint(uint64(now))...)
|
|
out = append(out, writeLengthDelimited(1, device)...)
|
|
}
|
|
|
|
// 1 个蜂窝基站
|
|
var cell []byte
|
|
cell = append(cell, writeTag(1, wireVarint)...)
|
|
cell = append(cell, writeVarint(1)...) // GSM
|
|
cell = append(cell, writeTag(2, wireVarint)...)
|
|
cell = append(cell, writeVarint(460)...) // MCC China
|
|
cell = append(cell, writeTag(3, wireVarint)...)
|
|
cell = append(cell, writeVarint(1)...) // MNC
|
|
cell = append(cell, writeTag(4, wireVarint)...)
|
|
cell = append(cell, writeVarint(15200)...) // LAC
|
|
cell = append(cell, writeTag(5, wireVarint)...)
|
|
cell = append(cell, writeVarint(24680)...) // CellID
|
|
out = append(out, writeLengthDelimited(5, cell)...)
|
|
|
|
return out
|
|
}
|