Files

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
}