mirror of
https://github.com/xweiba/location-spoofer.git
synced 2026-09-21 22:30:46 +08:00
618 lines
16 KiB
Go
618 lines
16 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
|
|
MotionSimulationEnabled bool
|
|
}
|
|
|
|
const (
|
|
motionActivityType = 63
|
|
motionActivityConfidence = 467
|
|
)
|
|
|
|
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}$`)
|
|
|
|
var wlocMarker = []byte{0, 0, 0, 1, 0, 0}
|
|
|
|
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
|
|
hasMotionType, hasMotionConfidence := false, 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...)
|
|
case c.MotionSimulationEnabled && f.num == 11 && f.wireType == wireVarint:
|
|
hasMotionType = true
|
|
raw := append(writeTag(11, wireVarint), writeVarint(motionActivityType)...)
|
|
if !bytes.Equal(raw, f.raw) {
|
|
changed = true
|
|
}
|
|
out = append(out, raw...)
|
|
case c.MotionSimulationEnabled && f.num == 12 && f.wireType == wireVarint:
|
|
hasMotionConfidence = true
|
|
raw := append(writeTag(12, wireVarint), writeVarint(motionActivityConfidence)...)
|
|
if !bytes.Equal(raw, f.raw) {
|
|
changed = true
|
|
}
|
|
out = append(out, raw...)
|
|
default:
|
|
out = append(out, f.raw...)
|
|
}
|
|
}
|
|
if c.MotionSimulationEnabled && !hasMotionType {
|
|
out = append(out, writeTag(11, wireVarint)...)
|
|
out = append(out, writeVarint(motionActivityType)...)
|
|
changed = true
|
|
}
|
|
if c.MotionSimulationEnabled && !hasMotionConfidence {
|
|
out = append(out, writeTag(12, wireVarint)...)
|
|
out = append(out, writeVarint(motionActivityConfidence)...)
|
|
changed = true
|
|
}
|
|
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 parseARPCPayloadBounds(body []byte) (lengthOffset, payloadOffset, payloadEnd int, err error) {
|
|
if len(body) < 2 {
|
|
return 0, 0, 0, errors.New("ARPC body too short")
|
|
}
|
|
|
|
offset := 2 // version
|
|
for range 3 {
|
|
if offset+2 > len(body) {
|
|
return 0, 0, 0, errors.New("truncated ARPC string length")
|
|
}
|
|
length := int(binary.BigEndian.Uint16(body[offset : offset+2]))
|
|
offset += 2
|
|
if length > len(body)-offset {
|
|
return 0, 0, 0, errors.New("truncated ARPC string")
|
|
}
|
|
offset += length
|
|
}
|
|
|
|
const functionAndLengthBytes = 8
|
|
if offset+functionAndLengthBytes > len(body) {
|
|
return 0, 0, 0, errors.New("truncated ARPC header")
|
|
}
|
|
lengthOffset = offset + 4
|
|
payloadOffset = lengthOffset + 4
|
|
payloadLength := uint64(binary.BigEndian.Uint32(body[lengthOffset:payloadOffset]))
|
|
if payloadLength == 0 || payloadLength > uint64(len(body)-payloadOffset) {
|
|
return 0, 0, 0, errors.New("invalid ARPC payload length")
|
|
}
|
|
return lengthOffset, payloadOffset, payloadOffset + int(payloadLength), nil
|
|
}
|
|
|
|
func patchARPCFrame(body []byte, c wlocCoords) ([]byte, patchStats, error) {
|
|
lengthOffset, payloadOffset, payloadEnd, err := parseARPCPayloadBounds(body)
|
|
if err != nil {
|
|
return nil, patchStats{}, err
|
|
}
|
|
|
|
var st patchStats
|
|
payload := body[payloadOffset:payloadEnd]
|
|
newPayload, changed, err := patchWlocPayload(payload, c, &st)
|
|
if err != nil {
|
|
return nil, patchStats{}, err
|
|
}
|
|
if !changed || bytes.Equal(newPayload, payload) {
|
|
return nil, patchStats{}, errors.New("ARPC envelope has no patchable wloc payload")
|
|
}
|
|
|
|
var lenBytes [4]byte
|
|
binary.BigEndian.PutUint32(lenBytes[:], uint32(len(newPayload)))
|
|
out := append(cloneBytes(body[:lengthOffset]), lenBytes[:]...)
|
|
out = append(out, newPayload...)
|
|
out = append(out, body[payloadEnd:]...)
|
|
return out, st, nil
|
|
}
|
|
|
|
func patchMarkerFrame(body []byte, c wlocCoords) ([]byte, patchStats, error) {
|
|
markerOffset := bytes.Index(body, wlocMarker)
|
|
if markerOffset < 0 {
|
|
return nil, patchStats{}, errors.New("wloc marker not found")
|
|
}
|
|
|
|
lengthOffset := markerOffset + len(wlocMarker)
|
|
payloadOffset := lengthOffset + 2
|
|
if payloadOffset > len(body) {
|
|
return nil, patchStats{}, errors.New("truncated marker frame")
|
|
}
|
|
payloadLength := int(binary.BigEndian.Uint16(body[lengthOffset:payloadOffset]))
|
|
if payloadLength == 0 || payloadLength > len(body)-payloadOffset {
|
|
return nil, patchStats{}, errors.New("invalid marker payload length")
|
|
}
|
|
payloadEnd := payloadOffset + payloadLength
|
|
|
|
var st patchStats
|
|
payload := body[payloadOffset:payloadEnd]
|
|
newPayload, changed, err := patchWlocPayload(payload, c, &st)
|
|
if err != nil {
|
|
return nil, patchStats{}, err
|
|
}
|
|
if !changed || bytes.Equal(newPayload, payload) {
|
|
return nil, patchStats{}, errors.New("marker frame has no patchable wloc payload")
|
|
}
|
|
if len(newPayload) > 65535 {
|
|
return nil, patchStats{}, errors.New("patched marker payload too large")
|
|
}
|
|
|
|
var lenBytes [2]byte
|
|
binary.BigEndian.PutUint16(lenBytes[:], uint16(len(newPayload)))
|
|
out := append(cloneBytes(body[:lengthOffset]), lenBytes[:]...)
|
|
out = append(out, newPayload...)
|
|
out = append(out, body[payloadEnd:]...)
|
|
return out, st, 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) {
|
|
if out, st, err := patchARPCFrame(body, c); err == nil {
|
|
return out, st, nil
|
|
}
|
|
if out, st, err := patchMarkerFrame(body, c); err == nil {
|
|
return out, st, nil
|
|
}
|
|
|
|
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
|
|
}
|