/* lexipwn (CLI version) Can do the following on Google Pixel 6 and up to Pixel 9 (requires root access): - manipulate devinfo partition (IMEIs, MACs, DSN, PSN, SKU etc) - fix IMEI SHA (the bootmode PS tag needs to be set to factory) - generate random but valid IMEIs based on the TAC/prefix - run arbitrary AT commands on the modem Usage: lexipwn [subcommand] [params] Available subcommands: * listps - list all tags in the PS tag area (TSV format) * setpsval [name1] [value1] [name2] [value2] ... - set tag values in the PS tag area * setpstype [name] [DIUS|DIFR] - set a tag type in the PS tag area * fiximeisha - actualize the IMEI SHA string in the persist partition (requires to be booted into the factory mode) * genimei [prefix] - generate a random valid IMEI starting with [prefix] (doesn't write this IMEI anywhere, so can be used without root) * atcmd [command] - run a single-line AT command and output its result Build with: GOOS=linux GOARCH=arm64 go build -a -trimpath -gcflags=all="-l -B -e" -ldflags "-s -w" (the result will be static; for dynamic PIE aarch64 builds, use GOOS=android instead) Created by Luxferre in 2024-2025, released into public domain */ package main import ( "os" "bytes" "encoding/binary" "fmt" "io" "strings" "strconv" "math/rand" "time" ) /* a structure to describe a single tag in the PS or PSENV section */ type DevInfoTag struct { Type [4]byte /* DIUS or DIFR */ FullLength uint32 /* name length + value length */ NameLength uint32 Name []byte Value []byte } /* a structure to describe the entire devinfo partition image */ /* note that the unused areas might actually be used by the device */ type DevInfoImage struct { Magic [4]byte /* must be DEVI */ VerMajor uint16 VerMinor uint16 UnusedArea1 [32]byte BoardProject uint16 BoardStageCode uint8 BoardRevHi uint8 BoardRevLo uint8 BoardVariant uint8 SlotARetryCount uint8 SlotAFlags uint8 SlotBRetryCount uint8 SlotBFlags uint8 UnusedArea2 [74]byte PStags []DevInfoTag PSENVtags []DevInfoTag } /* info area byte offsets */ const offs_SlotARetryCount = 48 const offs_SlotBRetryCount = 52 const offs_PStags = 128 const offs_PSENVtags = 4096 /* read the file data into the DevInfoImage structure */ func readDevInfo(data []byte) DevInfoImage { r := bytes.NewReader(data) var info DevInfoImage /* read the header area */ binary.Read(r, binary.BigEndian, &info.Magic) binary.Read(r, binary.LittleEndian, &info.VerMajor) binary.Read(r, binary.LittleEndian, &info.VerMinor) binary.Read(r, binary.BigEndian, &info.UnusedArea1) binary.Read(r, binary.LittleEndian, &info.BoardProject) binary.Read(r, binary.LittleEndian, &info.BoardStageCode) binary.Read(r, binary.LittleEndian, &info.BoardRevHi) binary.Read(r, binary.LittleEndian, &info.BoardRevLo) binary.Read(r, binary.LittleEndian, &info.BoardVariant) r.Seek(offs_SlotARetryCount, io.SeekStart) binary.Read(r, binary.LittleEndian, &info.SlotARetryCount) binary.Read(r, binary.LittleEndian, &info.SlotAFlags) r.Seek(offs_SlotBRetryCount, io.SeekStart) binary.Read(r, binary.LittleEndian, &info.SlotBRetryCount) binary.Read(r, binary.LittleEndian, &info.SlotBFlags) binary.Read(r, binary.BigEndian, &info.UnusedArea2) /* read the PS and PSENV tag areas */ info.PStags = make([]DevInfoTag, 0) info.PSENVtags = make([]DevInfoTag, 0) for { var tag DevInfoTag err := binary.Read(r, binary.BigEndian, &tag.Type) if err != nil { break } binary.Read(r, binary.LittleEndian, &tag.FullLength) binary.Read(r, binary.LittleEndian, &tag.NameLength) if tag.FullLength > 0 { if tag.NameLength > 0 { tag.Name = make([]byte, tag.NameLength) binary.Read(r, binary.BigEndian, &tag.Name) } tag.Value = make([]byte, tag.FullLength - tag.NameLength) binary.Read(r, binary.BigEndian, &tag.Value) } /* handle edge cases and populate the slices */ if tag.FullLength < 0xf00 && tag.NameLength > 0 { curpos, _ := r.Seek(0, io.SeekCurrent) if curpos < offs_PSENVtags { info.PStags = append(info.PStags, tag) } else { info.PSENVtags = append(info.PSENVtags, tag) } } } return info } /* write the DevInfoImage structure into 8192 bytes of data */ func writeDevInfo(info DevInfoImage) []byte { buf := new(bytes.Buffer) buf.Grow(8192) /* write the header area */ binary.Write(buf, binary.BigEndian, info.Magic) binary.Write(buf, binary.LittleEndian, info.VerMajor) binary.Write(buf, binary.LittleEndian, info.VerMinor) binary.Write(buf, binary.BigEndian, info.UnusedArea1) binary.Write(buf, binary.LittleEndian, info.BoardProject) binary.Write(buf, binary.LittleEndian, info.BoardStageCode) binary.Write(buf, binary.LittleEndian, info.BoardRevHi) binary.Write(buf, binary.LittleEndian, info.BoardRevLo) binary.Write(buf, binary.LittleEndian, info.BoardVariant) binary.Write(buf, binary.LittleEndian, uint16(0)) /* write two zeros */ binary.Write(buf, binary.LittleEndian, info.SlotARetryCount) binary.Write(buf, binary.LittleEndian, info.SlotAFlags) binary.Write(buf, binary.LittleEndian, uint16(0)) /* write two zeros again */ binary.Write(buf, binary.LittleEndian, info.SlotBRetryCount) binary.Write(buf, binary.LittleEndian, info.SlotBFlags) binary.Write(buf, binary.BigEndian, info.UnusedArea2) /* write the PS tag area */ var curoffs uint32 = offs_PStags for _, tag := range info.PStags { binary.Write(buf, binary.BigEndian, tag.Type) binary.Write(buf, binary.LittleEndian, tag.FullLength) binary.Write(buf, binary.LittleEndian, tag.NameLength) if tag.FullLength > 0 { if tag.NameLength > 0 { binary.Write(buf, binary.BigEndian, tag.Name) } binary.Write(buf, binary.BigEndian, tag.Value) } curoffs += 12 + tag.FullLength } /* write the last pseudo-tag of the section */ binary.Write(buf, binary.BigEndian, []byte("DIFR")) binary.Write(buf, binary.LittleEndian, uint32(offs_PSENVtags - curoffs - 12)) /* fill in the zeros before the PSENV section */ skip := int(offs_PSENVtags - curoffs - 8) for i:=0; i 0 { if tag.NameLength > 0 { binary.Write(buf, binary.BigEndian, tag.Name) } binary.Write(buf, binary.BigEndian, tag.Value) } curoffs += 12 + tag.FullLength } /* write the last pseudo-tag of the section */ binary.Write(buf, binary.BigEndian, []byte("DIFR")) skip = int(8184 - curoffs) binary.Write(buf, binary.LittleEndian, uint32(skip - 4)) for i:=0; i= 48 && d < 58 { dd = int(d) - 48 if i%2 == 1 { dd += dd if dd > 9 { dd -= 9 } } sum += dd } } sum %= 10 if sum != 0 { sum = 10 - sum } return sum } /* run a single AT command and attempt to read its result */ func runAtCmd(cmd string) string { ifacefile := "/dev/umts_router" rcmd := append([]byte(cmd), byte(13), byte(10)) /* add CRLF */ err := os.WriteFile(ifacefile, rcmd, 0666) /* write the request */ if err != nil { panic(err) } data, err := os.ReadFile(ifacefile) /* read the response */ if err != nil { panic(err) } return string(data) } /* entry point */ func main() { params := os.Args[1:] subcmd := params[0] fname := "/dev/block/bootdevice/by-name/devinfo" var info DevInfoImage /* preread the data if the subcommand is devinfo-related */ if subcmd == "listps" || subcmd == "setpsval" || subcmd == "setpstype" { data, err := os.ReadFile(fname) if err != nil { panic(err) } info = readDevInfo(data) } /* execute the subcommand */ switch subcmd { case "listps": for _, tag := range info.PStags { fmt.Printf("%s\t%s\t%s\n", tag.Type, tag.Name, tag.Value) } case "setpsval": valpairs := params[1:] plen := len(valpairs) if plen % 2 == 1 { fmt.Println("invalid parameter count!") return } for i := 0; i < plen; i+=2 { tagidx := findTag(info.PStags, valpairs[i]) if tagidx > -1 { setStringTag(&info.PStags[tagidx], valpairs[i+1]) } } os.WriteFile(fname, writeDevInfo(info), 0644) fmt.Println("devinfo image saved") case "setpstype": if len(params) != 3 { println("invalid parameter count!") return } tagtype := params[2] tagidx := findTag(info.PStags, params[1]) if tagidx > -1 { if tagtype != "DIFR" { /* only two types are allowed, force DIUS unless it's DIFR */ tagtype = "DIUS" } copy(info.PStags[tagidx].Type[:], []byte(tagtype)) os.WriteFile(fname, writeDevInfo(info), 0644) fmt.Println("devinfo image saved") } case "genimei": rand.Seed(time.Now().UTC().UnixNano()) var imeisb strings.Builder imeisb.WriteString(params[1]) /* start with the prefix */ for i:=0; i < 14; i++ { imeisb.WriteString(strconv.Itoa(rand.Intn(10))) } imei := imeisb.String()[:14] /* only take the first 14 digits of the result */ fmt.Printf("%s", imei + strconv.Itoa(calcLuhn(imei))) case "atcmd": if len(params) != 2 { println("invalid parameter count!") return } fmt.Printf("%s\n", runAtCmd(params[1])) case "fiximeisha": rawsharesult := runAtCmd("AT+GOOGGETIMEISHA") sharesult := strings.Split(strings.ReplaceAll(rawsharesult, "\r\n", "\n"), "\n") if len(sharesult) > 2 { if sharesult[2] == "ERROR" { fmt.Println("Error getting IMEI SHA, are you booted into the factory mode?") } else { shaparts := strings.Split(sharesult[2], ":") if len(shaparts) > 1 { targetfile := "/mnt/vendor/persist/modem/cpsha" os.WriteFile(targetfile, []byte(shaparts[1]), 0644) fmt.Println("CPSHA file written") } } } default: fmt.Println("Valid subcommands: listps, setpsval, setpstype, fiximeisha, genimei, atcmd") } }