Skip to content

Instantly share code, notes, and snippets.

@quarnster
Last active May 14, 2020 20:36
Show Gist options
  • Save quarnster/11189901 to your computer and use it in GitHub Desktop.
Save quarnster/11189901 to your computer and use it in GitHub Desktop.
// GOOS=linux GOARCH=386 go build verify.go && ftp -u boxeebox:/tmp/ verify
//
// This tool verifies the boxeebox boot block, based on
// findings from running the "bootblockverifier" tool found
// in the boxeebox rma image (see http://www.boxeeboxwiki.org/rma).
//
// ##################### WARNING!!!! ######################
// # Whether it's possible to use this information to #
// # actually replace the current bootloader with a #
// # custom version is at the moment unknown. Use at #
// # your own risk!! #
// ##################### WARNING!!!! ######################
//
// Running this tool alone should be safe as it only performs
// a read operation from nandflash, so should not be able to
// break anything.
//
// usage: go run verify.go
//
package main
import (
"bytes"
"crypto"
"crypto/rsa"
"crypto/sha256"
"encoding/hex"
"fmt"
"github.com/quarnster/util/encoding/binary"
"io"
"log"
"math/big"
"syscall"
"unsafe"
)
type (
Header struct {
Unknown [2]uint32
SomeSize uint32
Unknown2 uint32
VendorId uint32
Date uint32
Unknown3 [4]uint32
BodyLocation uint32 // "hex address of body location"
SignatureLocation uint32
KeyLocation uint32 // "(optional)hex address of Encrypted Ke location"
}
BLOCKNODE uint32
PAGENUMTYPE uint16
PAGESIZETYPE uint16
BLOCKSIZETYPE uint32
ADDRESSTYPE uintptr
BOOT_BLOCKTABLE_IO_CMD struct {
btEntrySizeInBits byte
btEntryNum uint16
AddrInRam ADDRESSTYPE
}
BOOT_BLOCKTABLE_INFO struct {
bt_sig [4]byte
nSpareSkipByteInZone1, nSpareSkipByteInZone0, reserve3, btEntrySizeInBits byte
btEntryNum, btOffsetInBytes, md5OffsetInBytes, md5SizeInBytes uint16
}
SPEC_IO_CMD struct {
NumPagesToTransfer uint32
AddrInRam ADDRESSTYPE
StartBlockNum BLOCKNODE
StartPageNum uint16
}
DEVICE_INFO struct {
wDeviceMaker uint16
wDeviceType uint32
wSpectraStartBlock BLOCKNODE
wSpectraEndBlock BLOCKNODE
wBlockNum BLOCKNODE
wTotalBlocks BLOCKNODE
wPagesPerBlock PAGENUMTYPE
wPageSize PAGESIZETYPE
wPageDataSize PAGESIZETYPE
wPageSpareSize PAGESIZETYPE
wNumPageSpareFlag uint16
wECCBytesPerSector uint16
wBlockSize BLOCKSIZETYPE
wBlockDataSize BLOCKSIZETYPE
wDataBlockNum BLOCKNODE
bPlaneNum byte
wDeviceMainAreaSize uint16
wDeviceSpareAreaSize uint16
wDevicesConnected uint16
wDeviceWidth uint16
wHWRevision uint16
wHWFeatures uint16
wONFIDevFeatures uint16
wONFIOptCommands uint16
wONFITimingMode uint16
wONFIPgmCacheTimingMode uint16
MLCDevice uint16
wSpareSkipBytes uint16
}
)
const (
GLOB_SBD_IOCTL_RD_MAIN = 0x8810 + iota
GLOB_SBD_IOCTL_WR_MAIN
GLOB_SBD_IOCTL_ERASE
GLOB_SBD_IOCTL_RD_ID
GLOB_SBD_IOCTL_CHK_BADBLK
GLOB_SBD_IOCTL_RD_MAIN_SPARE_RAW
GLOB_SBD_IOCTL_WR_MAIN_SPARE_RAW
)
const (
GLOB_SBD_IOCTL_ERASE_RAW = 0x9910 + iota
GLOB_SBD_IOCTL_RD_BT
GLOB_SBD_IOCTL_CREATE_BT_DUMMY
GLOB_SBD_IOCTL_CREATE_BT_BYERASE
GLOB_SBD_IOCTL_CREATE_BT_BYMARKER
GLOB_SBD_IOCTL_ERASE_BYMARKER
)
func leswap(data []byte) (cop []byte) {
cop = make([]byte, len(data))
for i := 0; i < len(data); i += 4 {
cop[i+0], cop[i+1], cop[i+2], cop[i+3] = data[i+3], data[i+2], data[i+1], data[i+0]
}
return
}
func ioctl(device int, cmd uintptr, data unsafe.Pointer) (int, error) {
a, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(device), cmd, uintptr(data))
var err error
if errno != 0 {
err = errno
}
return int(a), err
}
func readBootBlock() ([]byte, error) {
var (
id DEVICE_INFO
read SPEC_IO_CMD
)
device, err := syscall.Open("/dev/spectra", syscall.O_RDONLY, 0)
if err != nil || device < 0 {
return nil, fmt.Errorf("Device is negative: %d, %s", device, err)
}
defer syscall.Close(device)
fmt.Println("device:", device)
if _, err := ioctl(device, GLOB_SBD_IOCTL_RD_ID, unsafe.Pointer(&id)); err != nil {
return nil, err
}
fmt.Printf("Id: %+v\n", id)
data := make([]byte, int(id.wPageSize)*int(id.wPagesPerBlock))
read.StartBlockNum = 0
read.AddrInRam = ADDRESSTYPE(unsafe.Pointer(&data[0]))
read.NumPagesToTransfer = uint32(id.wPagesPerBlock)
ioctl(device, GLOB_SBD_IOCTL_RD_MAIN, unsafe.Pointer(&read))
fmt.Printf("Boot block: %+v\n", *(*BOOT_BLOCKTABLE_INFO)(unsafe.Pointer(&data[32])))
read.StartBlockNum = 8
if i, err := ioctl(device, GLOB_SBD_IOCTL_RD_MAIN, unsafe.Pointer(&read)); i == -1 || err != nil {
return nil, err
}
return data, nil
}
func main() {
data, err := readBootBlock()
if err != nil {
log.Fatalln(err)
}
var h Header
br := binary.BinaryReader{Reader: bytes.NewReader(data), Endianess: binary.LittleEndian}
br.ReadInterface(&h)
fmt.Printf("boot header: %+#v\n", h)
fmt.Printf("boot header: %+v\n", h)
// TODO: should probably read the data in 0x0 -> 0x30
// to derive these values rather than have
// them hardcoded.
keyBytes := leswap(data[0x94 : 0x94+256])
sig := leswap(data[h.SignatureLocation : h.SignatureLocation+256])
var key rsa.PublicKey
key.E = *(*int)(unsafe.Pointer(&data[0x94+256]))
key.N = big.NewInt(0)
key.N.SetBytes(keyBytes)
copy(data[0x80:], data[h.BodyLocation:])
hash := sha256.New()
io.Copy(hash, bytes.NewReader(data[:0xfb00]))
fmt.Println("Key:", key)
ha := hash.Sum(nil)
fmt.Printf("bootblock header:\n%s\nsha256 hash:\n%s\nsignature:\n%s\nkey:\n%s\n", hex.Dump(data[:0x80]), hex.Dump(ha), hex.Dump(sig), hex.Dump(keyBytes))
if rsa.VerifyPKCS1v15(&key, crypto.SHA256, ha, sig) == nil {
log.Println("Verification success!")
} else {
log.Println("Verification error")
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment