最近把很早以前购买的三星平板拿出来继续使用,其SoC是三星半导体自制的Exynos 9611,由于我买的是WIFI+LTE版本,其存在基带。很久以前用搭载Exynos4210的三星S2的时候,就对三星的奇怪的香农基带感到好奇。
历经快十年的对于逆向的爱好,最近再阅读了一些大牛写的代码和他们提供的Loader,成功的对目前最新的Exynos Shannon基带固件做到了解包,解压后可直接使用ShannonRE项目之中的Loader和静态分析工具进行分析。
当然如果你只是对三星的调试模式比较好奇的话,在我的解压工具中,其将自动从main区块中搜索AT指令,可通过终端模拟器连接到Samsung DM端口发送AT指令进行调试。
Pixel上的Tensor SoC中的基带,需要从运行时的设备直接Dump才可取得本工具可解析的镜像!

# siu.py v2
import struct
import re
import argparse
from contextlib import contextmanager
from dataclasses import dataclass
from typing import BinaryIO, Type, Pattern, Optional

@dataclass
class TOCHeader:
    name: str
    start: int
    size: int
    secdata: int
    queue: int

    def print_info(self):
        print(f"Block Name: {self.name}")
        print(f"Start Offset: 0x{self.start:08x}")
        print(f"Size: 0x{self.size:08x}")
        print(f"Sec Data: 0x{self.secdata:08x}")
        print(f"Queue: {self.queue}")

class ImageHandler:
    HEADER_SIZE = 32
    TOC_STRUCT_FORMAT = "<12s4xix4xiiii"
    
    def __init__(self, file_stream: BinaryIO):
        self.file = file_stream
    
    @contextmanager
    def _seek_context(self, position: Optional[int] = None):
        original = self.file.tell()
        try:
            if position is not None:
                self.file.seek(position)
            yield
        finally:
            self.file.seek(original)
            
    def read_toc_header(self, expected_name: str, position: Optional[int] = None) -> TOCHeader:
        with self._seek_context(position):
            try:
                buffer = self.file.read(self.HEADER_SIZE)
                if len(buffer) < self.HEADER_SIZE:
                    raise ValueError("Incomplete TOC header data")
                    
                fields = struct.unpack(self.TOC_STRUCT_FORMAT, buffer)
                name = fields[0].rstrip(b"\x00").decode("utf-8")
                
                if name != expected_name:
                    raise ValueError(f"Invalid TOC header. Expected {expected_name}, got {name}")
                    
                return TOCHeader(
                    name=name,
                    start=fields[1],
                    size=fields[2],
                    secdata=fields[3],
                    queue=fields[4]
                )
                
            except (struct.error, UnicodeDecodeError) as e:
                raise RuntimeError(f"Failed to parse TOC header: {e}") from e

    def extract_image(self, header: TOCHeader, image_type: str) -> bytes:
        with self._seek_context(header.start):
            return self.file.read(header.size)

    @staticmethod
    def find_strings(data: bytes, pattern: Pattern[bytes], filename: str) -> int:
        matches = re.findall(pattern, data)
        with open(filename, "wb") as f:
            for match in matches:
                f.write(match + b"\n")
        return len(matches)

class ShannonUnpacker:
    IMAGE_TYPES = ["BOOT", "MAIN", "VSS", "NV"]
    
    def __init__(self, filename: str):
        self.filename = filename
        self.handler: Optional[ImageHandler] = None
        self.headers = {}
        
    def process_file(self):
        with open(self.filename, "rb") as f:
            self.handler = ImageHandler(f)
            self._process_toc_headers()
            self._extract_images()
            
    def _process_toc_headers(self):
        try:
            # Initial TOC header validation
            toc_header = self.handler.read_toc_header("TOC")
            toc_header.print_info()
            
            # Process subsequent headers
            for img_type in self.IMAGE_TYPES:
                self.headers[img_type] = self.handler.read_toc_header(img_type)
                self.headers[img_type].print_info()
                
            # Process OFFSET header
            self.headers["OFFSET"] = self.handler.read_toc_header("OFFSET")
            self.headers["OFFSET"].print_info()
            
        except RuntimeError as e:
            print(f"[!] Processing error: {e}")
            raise
            
    def _extract_images(self):
        for img_type in self.IMAGE_TYPES:
            data = self.handler.extract_image(self.headers[img_type], img_type)
            with open(f"{img_type.lower()}.bin", "wb") as f:
                f.write(data)
            print(f"Extracted {img_type} image")

    def analyze_images(self, at_search: bool, version_pattern: Optional[str]):
        with open("main.bin", "rb") as f:
            main_data = f.read()
            
        if at_search:
            count = ImageHandler.find_strings(main_data, rb"AT\+[^\x00]*", "AT.TXT")
            print(f"Found {count} AT command strings")
            
        if version_pattern:
            pattern = re.escape(version_pattern).encode() + rb"[^\x00]*"
            count = ImageHandler.find_strings(main_data, pattern, "VER.TXT")
            if count > 0:
                with open("VER.TXT", "rb") as f:
                    print(f"Possible version: {f.readline().decode().strip()}")

def main():
    parser = argparse.ArgumentParser(
        description="SHANNON Image Unpacker v2",
        epilog="Xiang Xiang Cui Cui"
    )
    parser.add_argument("file", help="Input firmware file")
    parser.add_argument("--at", action="store_true", help="Extract AT commands")
    parser.add_argument("--ver", help="Search for version strings (provide model prefix)")
    
    args = parser.parse_args()
    
    try:
        unpacker = ShannonUnpacker(args.file)
        unpacker.process_file()
        unpacker.analyze_images(args.at, args.ver)
        
    except Exception as e:
        print(f"Critical error occurred: {e}")
        raise SystemExit(1) from e

if __name__ == "__main__":
    main()

本文及其附件均采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。

添加新评论