Created
October 26, 2022 01:46
-
-
Save shinyquagsire23/824d4e6b08379f3b537fa703d2b1874a to your computer and use it in GitHub Desktop.
Pixel Watch - Dump boot_a/boot_b from fastboot using sha1sum and ramdump memes
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Dump partitions from the Pixel Watch's fastboot using `oem sha1sum` | |
# and `oem ramdump` memes. | |
# | |
# Currently the first 8 bytes aren't bruteforced, but they ~can be. | |
# Doesn't really matter though because the first 8 bytes is the "ANDROID!" magic | |
# for boot partitions. | |
import os | |
import sys | |
import subprocess | |
import hashlib | |
import struct | |
# Which partition are we dumping? | |
dump_partition = "boot_b" | |
if len(sys.argv) >= 2: | |
dump_partition = sys.argv[1] | |
# Get the partition size from fastboot. | |
os.system("fastboot getvar partition-size:" + dump_partition + " 2> tmp_partsize.txt") | |
part_len = 0 | |
with open("tmp_partsize.txt", "r") as f: | |
part_len_str = f.read().split("\n")[0].split(": ")[1] | |
part_len = int(part_len_str, 16) | |
print ("Partition len:", hex(part_len)) | |
# Get the first sector hash so we can bruteforce the first 8 bytes | |
hash_str = "" | |
os.system("fastboot oem sha1sum " + dump_partition + " 0x0 0x200 2> tmp_hash.txt") | |
with open("tmp_hash.txt", "r") as f: | |
hash_str = f.read().split("\n")[1].split("(bootloader) ")[1] | |
# Prep/cleanup | |
os.system("mkdir -p partitions") | |
os.system("rm -f initial_dump.bin") | |
os.system("rm -f tmp_sector.bin") | |
os.system("rm -f tmp_partsize.txt") | |
# Get the first sector of boot_b into RAM, as a ground truth | |
os.system("fastboot oem sha1sum boot_b 0x0 0x200") | |
# Dump everything after the start of abl to find the heap. | |
os.system("fastboot oem ramdump stage_mem 0x9c800000 0x1000000") | |
os.system("fastboot get_staged initial_dump.bin") | |
# We're looking for "buildvariant=user" | |
find_initial = 0x0 | |
with open("initial_dump.bin", "rb") as f: | |
contents = f.read() | |
for i in range(0, len(contents) // 0x4): | |
offs = i*0x4 | |
if contents[offs:offs+0x10-1] == b'buildvariant=us': | |
find_initial = offs - 0x40 | |
break | |
if find_initial == 0: | |
print("Failed to find heap...") | |
sys.exit(-1) | |
# sha1sum allocates 0x100000 bytes, so our stride is one sector | |
# under that. | |
dump_stride_bytes = 0x100000 - 0x200 | |
dump_stride_sectors = dump_stride_bytes // 0x200 | |
dump_len = part_len | |
dump_left = dump_len | |
# Start dumping | |
with open("partitions/" + dump_partition + ".img", "wb") as f_out: | |
# Dump the first sector | |
os.system("fastboot oem sha1sum " + dump_partition + " 0 0x400") | |
os.system("fastboot oem ramdump stage_mem " + hex(0x9c800000 + find_initial) + " 0x200") | |
os.system("fastboot get_staged tmp_sector.bin") | |
# Read the first sector, and guess the first 8 bytes. | |
bruteforce_initial = struct.unpack("<Q", b"ANDROID!")[0] | |
contents_missing = struct.pack("<Q", bruteforce_initial) | |
with open("tmp_sector.bin", "rb") as f: | |
contents_firstsect = f.read()[:0x200] | |
not_found = False # don't bruteforce for now. | |
m = hashlib.sha1() | |
b = struct.pack("<Q", bruteforce_initial) | |
m.update(b) | |
m.update(contents_firstsect[8:]) | |
if m.hexdigest() == hash_str: | |
not_found = False | |
# If we're bruteforcing... | |
if not_found: | |
print ("Bruteforcing first 8 bytes...") | |
for bruteforce in range(0, 0xFFFFFFFFFFFFFFFF): | |
m = hashlib.sha1() | |
b = struct.pack("<Q", bruteforce) | |
m.update(b) | |
m.update(contents_firstsect[8:]) | |
if bruteforce % 0x10000 == 0: | |
print (hex(bruteforce)) | |
if m.hexdigest() == hash_str: | |
contents_firstsect = b | |
break | |
f_out.write(contents_missing) | |
f_out.write(contents_firstsect[8:]) | |
dump_left -= 0x200 | |
# Dump the rest of the sectors | |
for i in range(1, dump_len // 0x200, dump_stride_sectors): | |
offs_bytes = i * 0x200 | |
to_dump_bytes = dump_stride_bytes | |
if to_dump_bytes > dump_left: | |
to_dump_bytes = dump_left | |
# Dump the previous sector and desired sector, since the first 8 bytes get cut off in the alloc. | |
os.system("fastboot oem sha1sum " + dump_partition + " " + hex(offs_bytes - 0x200) + " " + hex(to_dump_bytes + 0x200)) | |
os.system("fastboot oem ramdump stage_mem " + hex(0x9c800000 + find_initial + 0x200) + " " + hex(dump_stride_bytes)) | |
os.system("fastboot get_staged tmp_sector.bin") | |
# Write sector | |
with open("tmp_sector.bin", "rb") as f: | |
contents = f.read()[:to_dump_bytes] | |
#print(hex(len(contents))) | |
f_out.write(contents) | |
dump_left -= dump_stride_bytes | |
# Cleanup | |
os.system("rm -f initial_dump.bin") | |
os.system("rm -f tmp_sector.bin") | |
os.system("rm -f tmp_partsize.txt") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment