HP MSM Support

Here is the script that is used to interrupt autoboot (it includes the shell access and cim decoder)

python script.py --action stopboot --serial_port COM5

Start the script before powering on the device. At least on MSM422 this is the interrupt sequence that is used here. After the script exists you can connect to the serial port and just hit enter and the U-Boot console will pop up

import argparse
import hashlib
import re
import serial
import serial.tools.list_ports as port_list
import struct

#from Cryptodome.Cipher import AES

class Serial:
    def __init__(self):
        pass

    def list_serial_ports(self):
        ports = list(port_list.comports())
        for p in ports:
            print (p)

    def stop_autoboot(self, serial_port):
        import serial
        import struct
        import re

        s = serial.Serial(serial_port, 115200, xonxoff=False, rtscts=False, dsrdtr=False)
        s.flushInput()
        s.flushOutput()
        read_data = ''
        found_autoboot = False
        print('[*] Looking for Autoboot countdown string on "'+ serial_port +'"')
        while True:
            bytesToRead = s.inWaiting()
            data_raw = s.read(bytesToRead)
            if len(data_raw) > 0:
                read_data += str(data_raw, 'utf-8', 'ignore')
                if re.findall('Autoboot countdown', read_data):
                    if not found_autoboot:
                        print('[*] Found Autoboot countdown string')
                        found_autoboot = True
                if found_autoboot:
                    print('[*] Sending interrupt string')
                    s.write(b'\x01\x0A')

                if re.findall('INTERRUPT', read_data):
                    break
                if re.findall('Unknown command', read_data):
                    break
        print('[*] U-Boot command prompt available. Connect to serial port "' + serial_port + '" using 115200 baud rate')


class File:
    def __init__(self):
        pass

    def read(self, path):
        strg = b""
        with open(path, "rb") as f:
            header = f.read(8)
            if header == b"CIM\xc1\x06\xad\xb0\x15":
                print('[*] CIM Header version 6 (AES)')
                print('[*] AES Encryption not supported ATM')
                exit()

                return decipher.decrypt(strg)
            else:
                print('[*] CIM Header version 5 (RC4)')
                f.seek(0, 0)

                while byte := f.read():
                    strg += byte
        return strg

    def write(self, path, data):
        f = open(path, "wb")
        f.write(data)
        f.close()

class ShellAccess:
    def __init__(self):
        self.challenge_static = b'\xa7\x25\x57\xd1\x90\x0e\x3d\x6b'
        self.sha = hashlib.sha1()

    def generate(self, challenge):
        self.sha.update(bytes(challenge[:3], 'utf-8'))
        self.sha.update(self.challenge_static)
        self.sha.update(bytes(challenge[3:], 'utf-8'))
        digest = self.sha.digest()
        return "%05u" % (digest[3] * digest[13])


class Firmware():
    def __init__(self):
        self.key = '379AeAB93l550g80'

    def crypt_rc4(self, data):
        return self.crypt_rc4_pp(data)

    def crypt_rc4_pp(self, data):
        S = list(range(256))
        j = 0
        key = self.key

        for i in list(range(256)):
            j = (j + S[i] + ord(key[i % len(key)])) % 256
            S[i], S[j] = S[j], S[i]

        j, y = (0, 0)
        out = bytearray()

        for char in data:
            j = (j + 1) % 256
            y = (y + S[j]) % 256
            S[j], S[y] = S[y], S[j]

            out.append(char ^ S[(S[j] + S[y]) % 256])

        return out


parser = argparse.ArgumentParser(description='Colubris/HPE Helper')
parser.add_argument('--action', help='[cim|shell|stopboot]')
parser.add_argument('--infile', help='input file to decrypt/encrypt')
parser.add_argument('--outfile', help='output file to decrypt/encrypt')
parser.add_argument('--challenge', help='challenge issues by en->sh')
parser.add_argument('--serial_port', help='serial port that is used to stop autoboot')

args = parser.parse_args()
if not args.action:
    print('[*] No action given.')
    exit()

if args.action == 'cim':
    if not args.infile or not args.outfile:
        print('[*] Both infile and outfile are needed')
        exit()
    fl = File()
    fw = Firmware()
    data = fw.crypt_rc4(fl.read(args.infile))
    fl.write(args.outfile, data)
elif args.action == 'shell':
    sa = ShellAccess()
    print( 'Response: %s' % sa.generate(args.challenge) )
elif args.action == 'stopboot':
    s = Serial()
    s.stop_autoboot(args.serial_port)

Latest version of the script (I will be waiting for some input if I should go further with this script/openwrt):

To decode CIM v5:

python script.py --action cim --infile cim_v5.cim --outfile cim_v5.tar

To access the shell:

python script.py --action shell --challenge 123456

To stop u-boot (start this before you start the device):

python script.py --action stopboot --serial-port COM5

To stop patch uimage (so that it's accepted by u-boot): (supported devices msm422|msm710|msm430|msm460)

python script.py --action patchheader --infile uImage.img --outfile uImage-patch.img --board-type msm422
import argparse
import hashlib
import re
import serial
import serial.tools.list_ports as port_list
import struct
import zlib
from struct import *

class UBoot:
    def __init__(self):
        self.header_format = '!7L4B32s'
        self.header_size = calcsize(self.header_format)

    def update_header(self, indata, sign_as):
        mx = 32
        indata = bytearray(indata)
        indata[mx:mx+32] = b'\x00' * 32
        indata[mx:mx+16] = b'5.0.0.0-sr9-007'
        if sign_as == 'msm422' or sign_as == 'msm710':
            indata[mx+16:mx+17] = b'\x81\xFF'
            indata[mx+21:mx+22] = b'\x20'
            indata[mx+25:mx+26] = b'\x40'
            indata[mx+28:mx+32] = b'\x51\x2D\x37\x9E'
        elif sign_as == 'msm430':
            indata[mx+16:mx+17] = b'\x82\xFF'
            indata[mx+21:mx+22] = b'\x20'
            indata[mx+25:mx+26] = b'\x40'
            indata[mx+28:mx+32] = b'\x59\xC3\xC9\x8E'
        elif sign_as == 'msm460':
            indata[mx+16:mx+17] = b'\x83\x01'
            indata[mx+21:mx+22] = b'\x20'
            indata[mx+25:mx+26] = b'\x40'
            indata[mx+28:mx+32] = b'\x59\xC3\xC9\xC1'

        hd = self.parse_header(indata[0:64])
        indata[4:8] = self.int_to_bytes(self.calculate_crc(hd), 4)
        print(self.int_to_bytes(self.calculate_crc(hd), 4))
        print(indata[0:64])
        return indata

    def parse_header(self, header_data):
        keys = ['magic', 'headerCrc', 'time', 'size', 'loadAddr', 'entryAddr', 'dataCrc', 'osType', 'arch', 'imageType', 'compression', 'name']

        values = unpack(self.header_format, header_data)
        hd = dict(zip(keys, values))

        if hd['imageType'] == 4:
            hd['files'] = getMultiFileLengths(fh, fh.tell())

        return hd

    def calculate_crc(self, hd):
        header = pack(self.header_format, hd['magic'], 0, hd['time'], hd['size'], hd['loadAddr'], hd['entryAddr'],
            hd['dataCrc'], hd['osType'], hd['arch'], hd['imageType'], hd['compression'], hd['name'])
        return (zlib.crc32(header) & 0xffffffff)

    def int_to_bytes(self, n, minlen=0):
        if n > 0:
            arr = []
            while n:
                n, rem = n >> 8, n & 0xff
                arr.append(rem)
            b = bytearray(reversed(arr))
        elif n == 0:
            b = bytearray(b'\x00')
        else:
            raise ValueError('Only non-negative values supported')

        if minlen > 0 and len(b) < minlen:
            b = (minlen-len(b)) * '\x00' + b
        return b

class Serial:
    def __init__(self):
        pass

    def list_serial_ports(self):
        ports = list(port_list.comports())
        for p in ports:
            print (p)

    def stop_autoboot(self, serial_port):
        s = serial.Serial(serial_port, 115200, xonxoff=False, rtscts=False, dsrdtr=False)
        s.flushInput()
        s.flushOutput()
        read_data = ''
        found_autoboot = False
        print('[*] Looking for Autoboot countdown string on "'+ serial_port +'"')
        while True:
            bytesToRead = s.inWaiting()
            data_raw = s.read(bytesToRead)
            if len(data_raw) > 0:
                read_data += str(data_raw, 'utf-8', 'ignore')
                if re.findall('Autoboot countdown', read_data):
                    if not found_autoboot:
                        print('[*] Found Autoboot countdown string')
                        found_autoboot = True
                if found_autoboot:
                    print('[*] Sending interrupt string')
                    s.write(b'\x01\x0A')

                if re.findall('INTERRUPT', read_data):
                    break
                if re.findall('Unknown command', read_data):
                    break
        print('[*] U-Boot command prompt available. Connect to serial port "' + serial_port + '" using 115200 baud rate')


class File:
    def __init__(self):
        pass

    def simple_read(self, path):
        strg = b""
        with open(path, "rb") as f:
            while byte := f.read():
                strg += byte
        return strg

    def read(self, path):
        strg = b""
        with open(path, "rb") as f:
            header = f.read(8)
            if header == b"CIM\xc1\x06\xad\xb0\x15":
                print('[*] CIM Header version 6 (AES)')
                print('[*] AES Encryption not supported ATM')
                exit()

                return
            else:
                print('[*] CIM Header version 5 (RC4)')
                f.seek(0, 0)

                while byte := f.read():
                    strg += byte
        return strg

    def write(self, path, data):
        f = open(path, "wb")
        f.write(data)
        f.close()

class ShellAccess:
    def __init__(self):
        self.challenge_static = b'\xa7\x25\x57\xd1\x90\x0e\x3d\x6b'
        self.sha = hashlib.sha1()

    def generate(self, challenge):
        self.sha.update(bytes(challenge[:3], 'utf-8'))
        self.sha.update(self.challenge_static)
        self.sha.update(bytes(challenge[3:], 'utf-8'))
        digest = self.sha.digest()
        return "%05u" % (digest[3] * digest[13])


class Firmware():
    def __init__(self):
        self.key = '379AeAB93l550g80'

    def crypt_rc4(self, data):
        return self.crypt_rc4_pp(data)

    def crypt_rc4_pp(self, data):
        S = list(range(256))
        j = 0
        key = self.key

        for i in list(range(256)):
            j = (j + S[i] + ord(key[i % len(key)])) % 256
            S[i], S[j] = S[j], S[i]

        j, y = (0, 0)
        out = bytearray()

        for char in data:
            j = (j + 1) % 256
            y = (y + S[j]) % 256
            S[j], S[y] = S[y], S[j]

            out.append(char ^ S[(S[j] + S[y]) % 256])

        return out


parser = argparse.ArgumentParser(description='Colubris/HPE Helper')
parser.add_argument('--action', help='[cim|shell|stopboot|patchheader]')
parser.add_argument('--infile', help='input file to decrypt/encrypt')
parser.add_argument('--outfile', help='output file to decrypt/encrypt')
parser.add_argument('--challenge', help='challenge issues by en->sh')
parser.add_argument('--serial-port', help='serial port that is used to stop autoboot')
parser.add_argument('--board-type', help='[msm422|msm710|msm430|msm460]')

args = parser.parse_args()
if not args.action:
    print('[*] No action given.')
    exit()

if args.action == 'cim':
    if not args.infile or not args.outfile:
        print('[*] Both infile and outfile are needed')
        exit()
    fl = File()
    fw = Firmware()
    data = fw.crypt_rc4(fl.read(args.infile))
    fl.write(args.outfile, data)
elif args.action == 'shell':
    sa = ShellAccess()
    print( 'Response: %s' % sa.generate(args.challenge) )
elif args.action == 'stopboot':
    s = Serial()
    s.stop_autoboot(args.serial_port)
elif args.action == 'patchheader':
    u = UBoot()
    fl = File()
    fc = fl.simple_read(args.infile)
    lfc = u.update_header(fc, args.board_type)
    fl.write(args.outfile, lfc)

Hi, This is a very interesting project.

I recently got a HP 425 Wireless Dual Radio 802.11n Access Point (JG654A).

The script works for the challenge.

The following command gives the same output:

getbootinfo -x 28
00 00 00 01

EDIT:

I dumped all mtd partitions as well as the bidio.

And the patching worked, too:


Changed it it:

Now this commands all give this answer (before it was 1):

# getbootinfo -x 28
80 00 00 00
# getbootinfo -x 28 0x80000000
80 00 00 00
# getbootinfo -d 29 8
8
# cat /proc/mtd
dev:    size   erasesize  name
mtd0: 00400000 00020000 "bw-kernel"
mtd1: 00400000 00020000 "kernel"
mtd2: 01400000 00020000 "bw-slash"
mtd3: 01600000 00020000 "flash"
mtd4: 00100000 00020000 "unused"
mtd5: 048c0000 00020000 "slash"
mtd6: 00010000 00010000 "pf"
mtd7: 000e0000 00010000 "boot"
mtd8: 00020000 00010000 "caldata"
mtd9: 000a0000 00010000 "bootware"
uname -a
Linux 127.0.0.1 2.6.34.1-colubris #1 Thu Jul 2 17:04:25 UTC 2015 mips unknown
# cat /proc/cpuinfo
system type             : Atheros AR934x
processor               : 0
cpu model               : MIPS 74Kc V4.12
BogoMIPS                : 279.55
wait instruction        : yes
microsecond timers      : yes
tlb_entries             : 32
extra interrupt vector  : yes
hardware watchpoint     : yes, count: 4, address/irw mask: [0x0000, 0x0ff8, 0x0ff8, 0x0ff8]
ASEs implemented        : mips16 dsp
shadow register sets    : 1
core                    : 0
VCED exceptions         : not available
VCEI exceptions         : not available

I just haven't figured out how to access the Serial ....

And I just discovered the Warranty is in effect, too, which is great, if I brick it, I can take it to HP.

Any news on this project?

I surfed the internals of this access point, and most if not all of the scripts are referring to MSM series. Seems this model is compatible.

Hi All,

Did anyone manage to flash OpenWRT onto the HP JG654A 425?

I happen to have access to a decent number of decommissioned MSM422 AP's J9359B's and am very interested in getting OpenWRT running on them, I'd be happy to hook up someone doing the serious work towards that goal with a good number of them for the cost of postage.
Needs to be someone capable of actually building and documenting a method of replacing the manufacturer firmware with OpenWRT... Not just someone wanting to score a bunch of free AP's. Reach out if that's you, these are located in QLD, Australia. Thanks.

Unfortunately I cannot continue working on this as I have bricked all 5 MSM422's that I had so until I can find some a bargain on some I won't be able to do anything. And shipping from Australia to Europe is kind of expensive :wink:

I have gotten my hands on 7 of these: https://fccid.io/RTP-MRLBB1003S

I'll be hacking on them in the other HP MSM thread once I receive them.

I had some old HP access points (V-M200 / J9467A / RSVLC-1001) laying around from my old WiFi deployment. I was able to open them up and connect to the UART and poke around. This trick of writing a patched bidio with the updated key worked on this model as well, and now I have access to UBoot. Happy to post any output if it would help anyone.

Hi alex,

How could you get the uImage.img file?

I have been playing with a HP 560 AP and I have got it to throw out some messages. Here are the steps that I took

  1. dump bidio
  2. edit bidio (with a hex editor) and search for "11 02 72 6F" and change that to "11 17 72 6F" and append after that " console=ttyS0,115200", after that you need to remove 21 bytes from the first part of the bid (Colubris_BID_$$$ should start at 0x0, 0x200, 0x400, 0x600) (I have no clue why it's there 4 times, but I think it's just the first one that's important)
  3. (you need to do this because uboot ignores bootcmd and sets it from here, appending root=/dev/...)
  4. build the image (or you can get it here )
    4a. I did chage the file displayed above removing the uboot header and generating another one with
mkimage -A ppc -O linux -T kernel -C none -a 0x01000000 -e 0x01000000 -n "6.6.9.1-23594" -d x1.noheader  uImage
  1. run the commands to boot it as shown below

Dump from my attempt:

=> tftp 0x1000000 uImage
eTSEC1: Link up, full duplex, 100 Mbps
Using eTSEC1 device
TFTP from server 192.168.1.111; our IP address is 192.168.1.1
Filename 'uImage'.
Load address: 0x1000000
Loading: #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################################
         #################################################
done
Bytes transferred = 12156584 (b97ea8 hex)
=> tftp 0xc00000 x.dtb
Using eTSEC1 device
TFTP from server 192.168.1.111; our IP address is 192.168.1.1
Filename 'x.dtb'.
Load address: 0xc00000
Loading: #
done
Bytes transferred = 8062 (1f7e hex)
=> bootm 0x1000000 - 0xc00000
read_vblock 6
read_vblock 3f7
Kernel arg: 'ro console=ttyS0,115200 root=/dev/mtdblock2'
## Booting image at 01000000 ...
   Image Name:   6.6.9.1-23594
   Image Type:   PowerPC Linux Kernel Image (uncompressed)
   Data Size:    12156520 Bytes = 11.6 MB
   Load Address: 01000000
   Entry Point:  01000000
   Verifying Checksum ... OK
   XIP Kernel Image ... OK
## Booting image2 at 01000000 ...
read_vblock 6
bid at fed0610 and ffee80
Uncompressed kernel size 9c43d02b, ram limit fe00000
## Loading RAMDisk Image at 9d43d02b ...
   Loading Ramdisk: ab6a5000..fdffe3d
Bad trap at PC: ff741dc, SR: 21200, vector=d00
NIP: 0FF741DC XER: 00000000 LR: 0FF8C880 REGS: 0fe4f0f8 TRAP: 0d00 DAR: FFFFEFFF
MSR: 00021200 EE: 0 PR: 0 FP: 0 ME: 1 IR/DR: 00

GPR00: 00000001 0FE4F1E8 0FE4F738 AB6A5000 FFFFF007 6475AE3C 0E266F9C 00606340
GPR08: 007C70FB 0FFCC6AC 00000020 0FE4EEE8 24002048 08A04029 0FFCF500 0EF6F000
GPR16: 9D006A00 0FE4F780 0FF877C4 00000008 00000000 0FE4F2E8 00FFFF0E AB6A5000
GPR24: 01000000 0FFF514C 00FFFF00 00FFF680 9C43D02B 9C43D02B 0FFD0214 0FDFFE3D
Call backtrace:
0FF8C860 0FF8D63C 0FFA209C 0FFA2580 0FF74628 0FF71630
Exception in kernel pc ff741dc signal 0
### ERROR ### Please RESET the board ###

Does anyone have any idea why this fails?

PS: if I use a image with load address and entry address set to 0 it always hangs after "Verifying Checksum ... OK"

I know @blocktrron was experimenting with the MSM560, and only abandoned it because of the low current supply on the mPCIe port.

I can't speak for how he got OpenWrt booting ... assuming he does not weigh in, let's start with your messages:

Bad trap at PC: ff741dc, SR: 21200, vector=d00
NIP: 0FF741DC XER: 00000000 LR: 0FF8C880 REGS: 0fe4f0f8 TRAP: 0d00 DAR: FFFFEFFF
MSR: 00021200 EE: 0 PR: 0 FP: 0 ME: 1 IR/DR: 00
:
Call backtrace:
0FF8C860 0FF8D63C 0FFA209C 0FFA2580 0FF74628 0FF71630
Exception in kernel pc ff741dc signal 0

So $PC=0xff741dc -- that's not the kernel, that's still u-boot. It's during the RAMdisk loading stage that things fail:

ab6a5000..fdffe3d

That's not a forward range :^)

Try building a dummy ramdisk and feeding it to bootm as the second argument instead of -. Seems like it insists it still needs one.

You said it ignores your bootcmd -- does it actually do that when autobooting, or does it just ignore the arguments to bootm? If so, we can try go and try to come up with a custom loader.

It actually reads whatever is in bidio at location 0x11 (not address 0x11) and it appends "root=/dev/mtdblock2" to it.
By default bidio is "0x11 0x02 ro". I have changed mine to be "0x11 0x17 ro console=ttyS0,115200" (and have removed the extra bits from the end to still match 0x200 to be the Colubrid thing
The thing is that if I try to boot a image 3 times it appends "root=/dev/mtdblock2" 3 times
so everything in bootarg is ignored completely,
I have also tried building a FIT image but for some reason it throws a "bad magic" error but if I do a iminfo it actually sees it and describes it correctly.
I will try to build a kernel and initrd separately and see if that works

Ah, I see.

I've reached out to @blocktrron as to how he's done this.

1 Like

So I have tried to add an initrd but for some reason it tries to boot image2 from the same location as image

=> bootm 0x1000000 0x2000000 0xc00000
read_vblock 6
read_vblock 3f7
Kernel arg: 'ro console=ttyS0,115200 root=/dev/mtdblock2'
## Booting image at 01000000 ...
   Image Name:   6.6.9.1-23594
   Image Type:   PowerPC Linux Kernel Image (uncompressed)
   Data Size:    3234068 Bytes =  3.1 MB
   Load Address: 01000000
   Entry Point:  01000000
   Verifying Checksum ... OK
   XIP Kernel Image ... OK
## Booting image2 at 01000000 ...
read_vblock 6
bid at fed0610 and ffee80
Uncompressed kernel size 9bba25f5, ram limit fe00000
## Loading RAMDisk Image at 9cba25f5 ...
   Loading Ramdisk: ab68c000..fdff31f
Bad trap at PC: ff741d8, SR: 21200, vector=d00
NIP: 0FF741D8 XER: 00000000 LR: 0FF8C880 REGS: 0fe4f0f8 TRAP: 0d00 DAR: FFFFEFFD
MSR: 00021200 EE: 0 PR: 0 FP: 0 ME: 1 IR/DR: 00

GPR00: 00000003 0FE4F1E8 0FE4F738 AB68C000 FFFFF001 6477331C 0EAE89CC 6340007C
GPR08: 60800060 0FFCC6AC 00000020 0FE4EEE8 24002048 00A04129 0FFCF500 0EF6F000
GPR16: 9D006A00 0FE4F780 0FF877C4 00000008 00000000 0FE4F2E8 00FFFF0E AB68C000
GPR24: 01000000 0FFF514C 00FFFF00 00FFF680 9BBA25F5 9BBA25F5 0FFD0214 0FDFF31F
Call backtrace:
0FF8C860 0FF8D63C 0FFA209C 0FFA2580 0FF74628 0FF71630
Exception in kernel pc ff741d8 signal 0
### ERROR ### Please RESET the board ###
=> bootm 0x200000
read_vblock 6
read_vblock 3f7
Kernel arg: 'ro console=ttyS0,115200 root=/dev/mtdblock2'
## Booting image at 00200000 ...
   Image Name:   6.6.9.1-23594
   Image Type:   PowerPC Linux Kernel Image (uncompressed)
   Data Size:    3234068 Bytes =  3.1 MB
   Load Address: 00000000
   Entry Point:  00000000
   Verifying Checksum ... OK
OK
## Booting image2 at 00200000 ...
read_vblock 6
bid at fed0610 and ffee80
Uncompressed kernel size fe4f448, ram limit fe00000
## Loading RAMDisk Image at 9bda1ace ...
Kernel and ram disk do not fit together
### ERROR ### Please RESET the board ###

now what image2 is I have no clue

So, Blocktrron replaced U-boot entirely: https://github.com/blocktrron/u-boot-msm

I'll drop him a message to ask about a .config and how he got the addresses. Thanks

Hi Alex,

Any update on this, I have some MSM460 and I can help to test

Blocktrron has a new commit for msm460 https://git.openwrt.org/?p=openwrt/staging/blocktrron.git;a=log;h=refs/heads/msm460-2024

For other readers, see OpenWrt support for HP Prototype Access Point? - #47 by hurricos