// Copyright (c) 2018, Rene Lergner - wpinternals.net - @Heathcliff74xda // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software"), // to deal in the Software without restriction, including without limitation // the rights to use, copy, modify, merge, publish, distribute, sublicense, // and/or sell copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; using System.Collections; using System.IO; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; namespace WPinternals { internal enum SaharaMode : uint { ImageTransferPending = 0x00, ImagetransferComplete = 0x01, MemoryDebug = 0x02, Command = 0x03 } internal delegate void ReadyHandler(); internal class QualcommSahara { private readonly QualcommSerial Serial; public QualcommSahara(QualcommSerial Serial) { Serial.EncodeCommands = false; Serial.DecodeResponses = false; this.Serial = Serial; } public bool SendImage(string Path) { bool Result = true; LogFile.Log("Sending programmer: " + Path, LogType.FileOnly); int Step = 0; UInt32 Offset = 0; UInt32 Length = 0; byte[] ImageBuffer = null; try { Step = 1; byte[] Hello = Serial.GetResponse(new byte[] { 0x01, 0x00, 0x00, 0x00 }); // Incoming Hello packet: // 00000001 = Hello command id // xxxxxxxx = Length // xxxxxxxx = Protocol version // xxxxxxxx = Supported version // xxxxxxxx = Max packet length // xxxxxxxx = Expected mode // 6 dwords reserved space LogFile.Log("Protocol: 0x" + ByteOperations.ReadUInt32(Hello, 0x08).ToString("X8"), LogType.FileOnly); LogFile.Log("Supported: 0x" + ByteOperations.ReadUInt32(Hello, 0x0C).ToString("X8"), LogType.FileOnly); LogFile.Log("MaxLength: 0x" + ByteOperations.ReadUInt32(Hello, 0x10).ToString("X8"), LogType.FileOnly); LogFile.Log("Mode: 0x" + ByteOperations.ReadUInt32(Hello, 0x14).ToString("X8"), LogType.FileOnly); // Packet: // 00000002 = Hello response command id // 00000030 = Length // 00000002 = Protocol version // 00000001 = Supported version // 00000000 = Status OK // 00000000 = Mode // rest is reserved space Step = 2; byte[] HelloResponse = new byte[] { 0x02, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; Serial.SendData(HelloResponse); Step = 3; using FileStream FileStream = new(Path, FileMode.Open, FileAccess.Read); while (true) { Step = 4; byte[] ReadDataRequest = Serial.GetResponse(null); UInt32 ResponseID = ByteOperations.ReadUInt32(ReadDataRequest, 0); if (ResponseID == 4) { break; } if (ResponseID != 3) { Step = 5; throw new BadConnectionException(); } Offset = ByteOperations.ReadUInt32(ReadDataRequest, 0x0C); Length = ByteOperations.ReadUInt32(ReadDataRequest, 0x10); if ((ImageBuffer == null) || (ImageBuffer.Length != Length)) { ImageBuffer = new byte[Length]; } if (FileStream.Position != Offset) { FileStream.Seek(Offset, SeekOrigin.Begin); } Step = 6; FileStream.Read(ImageBuffer, 0, (int)Length); Step = 7; Serial.SendData(ImageBuffer); } } catch (Exception Ex) { LogFile.LogException(Ex, LogType.FileAndConsole, Step.ToString() + " 0x" + Offset.ToString("X8") + " 0x" + Length.ToString("X8")); Result = false; } if (Result) { LogFile.Log("Programmer loaded into phone memory", LogType.FileOnly); } return Result; } public bool Handshake() { bool Result = true; try { byte[] Hello = Serial.GetResponse(new byte[] { 0x01, 0x00, 0x00, 0x00 }); // Incoming Hello packet: // 00000001 = Hello command id // xxxxxxxx = Length // xxxxxxxx = Protocol version // xxxxxxxx = Supported version // xxxxxxxx = Max packet length // xxxxxxxx = Expected mode // 6 dwords reserved space LogFile.Log("Protocol: 0x" + ByteOperations.ReadUInt32(Hello, 0x08).ToString("X8"), LogType.FileOnly); LogFile.Log("Supported: 0x" + ByteOperations.ReadUInt32(Hello, 0x0C).ToString("X8"), LogType.FileOnly); LogFile.Log("MaxLength: 0x" + ByteOperations.ReadUInt32(Hello, 0x10).ToString("X8"), LogType.FileOnly); LogFile.Log("Mode: 0x" + ByteOperations.ReadUInt32(Hello, 0x14).ToString("X8"), LogType.FileOnly); byte[] HelloResponse = new byte[] { 0x02, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; byte[] Ready = Serial.SendCommand(HelloResponse, new byte[] { 0x03, 0x00, 0x00, 0x00 }); } catch { Result = false; } return Result; } public void ResetSahara() { Serial.SendCommand(new byte[] { 0x07, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00 }, new byte[] { 0x08, 0x00, 0x00, 0x00 }); } public bool ConnectToProgrammer(byte[] PacketFromPcToProgrammer) { // Behaviour of old firehose: // Takes about 20 ms to be started. // Then PC has to start talking to the phone. // Behaviour of new firehose: // After 2000 ms the firehose starts talking to the PC // // For the duration of 2.5 seconds we will send Hello packages // And also wait for incoming messages // An incoming message can be a response to our outgoing Hello packet (read incoming until "response value") // Or it can be an incoming Hello-packet from the programmer (always 2 packets, starting with "Chip serial num") // Sending the hello-packet can succeed immediately, or it can timeout. // When sending succeeds, an answer should be incoming immediately to complete the handshake. // When an incoming Hello was received, the phone still expects to receive another Hello. int HelloSendCount = 0; bool HandshakeCompleted = false; string Incoming; do { Serial.SetTimeOut(200); HelloSendCount++; try { LogFile.Log("Send Hello to programmer (" + HelloSendCount.ToString() + ")", LogType.FileOnly); Serial.SendData(PacketFromPcToProgrammer); LogFile.Log("Hello packet accepted", LogType.FileOnly); } catch { LogFile.Log("Hello packet not accepted", LogType.FileOnly); } try { Serial.SetTimeOut(500); Incoming = Encoding.ASCII.GetString(Serial.GetResponse(null)); LogFile.Log("In: " + Incoming, LogType.FileOnly); Serial.SetTimeOut(200); if (Incoming.Contains("Chip serial num")) { Incoming = Encoding.ASCII.GetString(Serial.GetResponse(null)); LogFile.Log("In: " + Incoming, LogType.FileOnly); LogFile.Log("Incoming Hello-packets received", LogType.FileOnly); } while (Incoming.IndexOf("response value") < 0) { Incoming = Encoding.ASCII.GetString(Serial.GetResponse(null)); LogFile.Log("In: " + Incoming, LogType.FileOnly); } LogFile.Log("Incoming Hello-response received", LogType.FileOnly); if (!Incoming.Contains("Failed to authenticate Digital Signature.")) { HandshakeCompleted = true; } else { LogFile.Log("Programmer failed to authenticate Digital Signature", LogType.FileOnly); } } catch { } } while (!HandshakeCompleted && (HelloSendCount < 6)); return HandshakeCompleted; } public bool ConnectToProgrammerInTestMode() { byte[] HelloPacketFromPcToProgrammer = new byte[0x20C]; ByteOperations.WriteUInt32(HelloPacketFromPcToProgrammer, 0, 0x57503730); ByteOperations.WriteUInt32(HelloPacketFromPcToProgrammer, 0x28, 0x57503730); ByteOperations.WriteUInt32(HelloPacketFromPcToProgrammer, 0x208, 0x57503730); ByteOperations.WriteUInt16(HelloPacketFromPcToProgrammer, 0x48, 0x4445); bool HandshakeCompleted = ConnectToProgrammer(HelloPacketFromPcToProgrammer); if (HandshakeCompleted) { LogFile.Log("Handshake completed with programmer in testmode", LogType.FileOnly); } else { LogFile.Log("Handshake with programmer failed", LogType.FileOnly); } return HandshakeCompleted; } public async Task Reset(string ProgrammerPath) { bool SendImageResult = await Task.Run(() => SendImage(ProgrammerPath)); if (!SendImageResult) { return false; } await Task.Run(() => StartProgrammer()); bool Connected = await Task.Run(() => ConnectToProgrammerInTestMode()); if (!Connected) { return false; } LogFile.Log("Rebooting phone", LogType.FileAndConsole); const string Command03 = ""; LogFile.Log("Out: " + Command03, LogType.FileOnly); Serial.SendData(Encoding.ASCII.GetBytes(Command03)); string Incoming; do { Incoming = Encoding.ASCII.GetString(Serial.GetResponse(null)); LogFile.Log("In: " + Incoming, LogType.FileOnly); } while (Incoming.IndexOf("response value") < 0); // Workaround for problem // SerialPort is sometimes not disposed correctly when the device is already removed. // So explicitly dispose here Serial.Close(); return true; } public void SwitchMode(SaharaMode Mode) { byte[] SwitchModeCommand = new byte[] { 0x0C, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; ByteOperations.WriteUInt32(SwitchModeCommand, 8, (UInt32)Mode); byte[] ResponsePattern = null; switch (Mode) { case SaharaMode.ImageTransferPending: ResponsePattern = new byte[] { 0x04, 0x00, 0x00, 0x00 }; break; case SaharaMode.MemoryDebug: ResponsePattern = new byte[] { 0x09, 0x00, 0x00, 0x00 }; break; case SaharaMode.Command: ResponsePattern = new byte[] { 0x0B, 0x00, 0x00, 0x00 }; break; } Serial.SendCommand(SwitchModeCommand, ResponsePattern); } public void StartProgrammer() { LogFile.Log("Starting programmer", LogType.FileAndConsole); byte[] DoneCommand = new byte[] { 0x05, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00 }; bool Started = false; int count = 0; do { count++; try { byte[] DoneResponse = Serial.SendCommand(DoneCommand, new byte[] { 0x06, 0x00, 0x00, 0x00 }); Started = true; } catch (BadConnectionException) { LogFile.Log("Problem while starting programmer. Attempting again.", LogType.FileAndConsole); } } while (!Started && count < 3); if (count >= 3 && !Started) { LogFile.Log("Maximum number of attempts to start the programmer exceeded.", LogType.FileAndConsole); throw new BadConnectionException(); } LogFile.Log("Programmer being launched on phone", LogType.FileOnly); } public async Task SendEdPayload(string ProgrammerPath, string PayloadPath) { // First, let's read the Emergency Download payload header and verify its validity FileStream PayloadStream = File.OpenRead(PayloadPath); byte[] ValidReferencePayloadHeader = new byte[] { 0x45, 0x6D, 0x65, 0x72, 0x67, 0x65, 0x6E, 0x63, 0x79, 0x20, 0x50, 0x61, 0x79, 0x6C, 0x6F, 0x61, 0x64 }; byte[] PayloadHeader = new byte[17]; PayloadStream.Read(PayloadHeader, 0, 17); bool IsValidEdPayloadImage = StructuralComparisons.StructuralEqualityComparer.Equals(PayloadHeader, ValidReferencePayloadHeader); if (!IsValidEdPayloadImage) { return false; } // Now let's read the information block PayloadStream.Seek(0x64, SeekOrigin.Begin); byte[] PayloadInformationBlock = new byte[0x64]; PayloadStream.Read(PayloadInformationBlock, 0, 0x64); string buildtime = Encoding.ASCII.GetString(PayloadInformationBlock).Trim('\0'); PayloadStream.Read(PayloadInformationBlock, 0, 0x64); string builddate = Encoding.ASCII.GetString(PayloadInformationBlock).Trim('\0'); PayloadStream.Read(PayloadInformationBlock, 0, 0x64); string version = Encoding.ASCII.GetString(PayloadInformationBlock).Trim('\0'); PayloadInformationBlock = new byte[0x670]; PayloadStream.Read(PayloadInformationBlock, 0, 0x670); string Info = Encoding.ASCII.GetString(PayloadInformationBlock).Trim('\0'); // Print some information about the payload LogFile.Log("Emerency flasher version 0.1", LogType.FileAndConsole); LogFile.Log("Programmer information:", LogType.FileAndConsole); LogFile.Log("Build time: " + buildtime, LogType.FileAndConsole); LogFile.Log("Build date: " + builddate, LogType.FileAndConsole); LogFile.Log("Version: " + version, LogType.FileAndConsole); LogFile.Log("Info: " + Info, LogType.FileAndConsole); // Send the emergency programmer to the phone bool SendImageResult = await Task.Run(() => SendImage(ProgrammerPath)); if (!SendImageResult) { return false; } // Start the emergency programmer on the phone await Task.Run(() => StartProgrammer()); // Wait a few seconds before sending commands LogFile.Log("Waiting...", LogType.FileAndConsole); Thread.Sleep(2000); LogFile.Log("Waiting...OK", LogType.FileAndConsole); bool Terminated = false; bool Connected = false; bool ProgrammerRawMode = false; string Incoming; while (!Terminated) { PayloadInformationBlock = new byte[0x200]; PayloadStream.Read(PayloadInformationBlock, 0, 0x200); string ProgrammerCommand = Encoding.ASCII.GetString(PayloadInformationBlock.Skip(0xC).ToArray()).Trim('\0'); LogFile.Log(ProgrammerCommand, LogType.FileAndConsole); byte[] PacketFromPcToProgrammer = Array.Empty(); byte[] temp = new byte[0x200]; while (true) { if (PayloadStream.Position == PayloadStream.Length) { Terminated = true; break; } PayloadStream.Read(temp, 0, 0x200); if (temp[12] == 77 && temp[13] == 83 && temp[14] == 71 && temp[15] == 95) { PayloadStream.Seek(-0x200, SeekOrigin.Current); break; } PacketFromPcToProgrammer = PacketFromPcToProgrammer.Concat(temp).ToArray(); } bool ExpectingReplyFromProgrammer = false; if (ProgrammerCommand.Contains("XML")) { string Outgoing = Encoding.ASCII.GetString(PacketFromPcToProgrammer).Trim('\0'); PacketFromPcToProgrammer = Encoding.ASCII.GetBytes(Outgoing); LogFile.Log("Out: " + Outgoing, LogType.FileAndConsole); } if (!ProgrammerCommand.Contains("RAW_DATA") && !ProgrammerRawMode) { ExpectingReplyFromProgrammer = true; } if (ProgrammerCommand.Contains("LAST") && ProgrammerRawMode) { ExpectingReplyFromProgrammer = true; } if (ProgrammerCommand.Contains("DATA_ALL") && ProgrammerRawMode) { ExpectingReplyFromProgrammer = true; } if (ProgrammerCommand.Contains("RAW_DATA") && !ProgrammerRawMode) { LogFile.Log("Phone is not in raw mode ON, leaving...", LogType.FileAndConsole); // Workaround for problem // SerialPort is sometimes not disposed correctly when the device is already removed. // So explicitly dispose here Serial.Close(); LogFile.Log("Phone has been emergency flashed unsuccessfully!", LogType.FileAndConsole); PayloadStream.Dispose(); return false; } if (!ProgrammerCommand.Contains("RAW_DATA") && ProgrammerRawMode) { LogFile.Log("Phone is not in raw mode ON, leaving...", LogType.FileAndConsole); // Workaround for problem // SerialPort is sometimes not disposed correctly when the device is already removed. // So explicitly dispose here Serial.Close(); LogFile.Log("Phone has been emergency flashed unsuccessfully!", LogType.FileAndConsole); PayloadStream.Dispose(); return false; } if (Connected) { Serial.SendData(PacketFromPcToProgrammer); } if (ExpectingReplyFromProgrammer) { if (!Connected) { Connected = ConnectToProgrammer(PacketFromPcToProgrammer); if (Connected) { LogFile.Log("Handshake completed with programmer in validated image programming (VIP) mode", LogType.FileAndConsole); } else { LogFile.Log("Handshake with programmer failed", LogType.FileAndConsole); } if (!Connected) { LogFile.Log("Phone programmer is now ignoring us, leaving...", LogType.FileAndConsole); // Workaround for problem // SerialPort is sometimes not disposed correctly when the device is already removed. // So explicitly dispose here Serial.Close(); LogFile.Log("Phone has been emergency flashed unsuccessfully!", LogType.FileAndConsole); PayloadStream.Dispose(); return false; } } else { do { Serial.SetTimeOut(500); Incoming = Encoding.ASCII.GetString(Serial.GetResponse(null)); Serial.SetTimeOut(200); LogFile.Log("In: " + Incoming, LogType.FileAndConsole); } while (Incoming.IndexOf("response value") < 0); if (Incoming.Contains("rawmode=\"false\"")) { ProgrammerRawMode = false; LogFile.Log("Raw mode: OFF", LogType.FileAndConsole); } if (Incoming.Contains("rawmode=\"true\"")) { ProgrammerRawMode = true; LogFile.Log("Raw mode: ON", LogType.FileAndConsole); } if (!Incoming.Contains("ACK")) { LogFile.Log("Phone programmer is now ignoring us, leaving...", LogType.FileAndConsole); // Workaround for problem // SerialPort is sometimes not disposed correctly when the device is already removed. // So explicitly dispose here Serial.Close(); LogFile.Log("Phone has been emergency flashed unsuccessfully!", LogType.FileAndConsole); PayloadStream.Dispose(); return false; } } } } // Workaround for problem // SerialPort is sometimes not disposed correctly when the device is already removed. // So explicitly dispose here Serial.Close(); LogFile.Log("Phone has been emergency flashed successfully!", LogType.FileAndConsole); PayloadStream.Dispose(); return true; } } }