├── .gitignore ├── LICENSE ├── README.md ├── conf ├── 29F2F108_Micron.conf ├── 29F4G08_Micron_ATMEL.conf ├── K9F1G08U0D_Samsung.conf ├── MX30LF2G18AC_NXP_IMX28.conf └── S34ML01G2_ATMEL.conf ├── images └── exploiting_unencrypted_nand_poc_video.jpg ├── nand_dump_decoder.py ├── nand_dump_decoder.sh ├── nand_dump_encoder.py ├── nand_dump_encoder.sh ├── requirements.txt ├── setup.sh └── yaffs_ecc.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | *.pyc 3 | *.log 4 | *.bin 5 | venv/* 6 | .venv/* 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018-2020 SySS Research 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NAND Dump Tools 2 | 3 | The SySS NAND Dump Tools are a collection of software tools for working with raw NAND flash memory dumps and images. 4 | 5 | By using error-correcting codes, for instance [BCH codes (Bose–Chaudhuri–Hocquenghem Codes)](https://en.wikipedia.org/wiki/BCH_code), supporting different configurations, the **NAND Dump Decoder** can create error-corrected data dumps from raw NAND flash memory dumps, which can then be used with other tools, for instance the [ubireader tools](https://github.com/jrspruitt/ubi_reader). 6 | 7 | The **NAND Dump Encoder** can be used to create raw NAND flash memory images from binary input files for supported target platforms, which can then be directly written to corresponding NAND memory chips. 8 | 9 | The NAND Dump Tools are based on and inspired by the open source software tool [PMECC Reader and Decoder](https://www.mickaelwalter.fr/2018/06/08/dumping-a-slc-nand-flash-with-atmel-pmecc/) by Mickaël Walter ([@MickaelWalter](https://twitter.com/MickaelWalter)). 10 | 11 | ## Supported NAND modes/target platforms 12 | 1. ATMEL PMECC (BCH) 13 | 2. NXP i.MX28 (BCH) 14 | 3. NXP P1014 (BCH) 15 | 4. YAFFS2 (Hamming code, experimental) 16 | 17 | ## Installation 18 | 19 | The NAND Dump Tools can be downloaded and installed in the following way: 20 | 21 | ``` 22 | git clone https://github.com/SySS-Research/nand-dump-tools.git 23 | cd NAND_Dump_Tools 24 | ./setup.sh 25 | ``` 26 | 27 | ## Usage 28 | 29 | The Python tools **NAND Dump Decoder** and **NAND Dump Encoder** can be used via Shell script wrappers which automatically use the virtual environment for Python created during the software installation. 30 | 31 | The following output shows the help screen of the **NAND Dump Decoder**: 32 | 33 | ``` 34 | $ ./nand_dump_decoder.sh --help 35 | _ _ ___ _ _______ ______ ______ _ 36 | | \ | | / _ \ | \ | | _ \ | _ \ | _ \ | | 37 | | \| |/ /_\ \| \| | | | | | | | |_ _ _ __ ___ _ __ | | | |___ ___ ___ __| | ___ _ __ 38 | | . ` || _ || . ` | | | | | | | | | | | '_ ` _ \| '_ \ | | | / _ \/ __/ _ \ / _` |/ _ \ '__| 39 | | |\ || | | || |\ | |/ / | |/ /| |_| | | | | | | |_) | | |/ / __/ (_| (_) | (_| | __/ | 40 | \_| \_/\_| |_/\_| \_/___/ |___/ \__,_|_| |_| |_| .__/ |___/ \___|\___\___/ \__,_|\___|_| 41 | | | 42 | |_| 43 | NAND Dump Decoder v0.2 by Matthias Deeg - SySS GmbH (c) 2018-2020 44 | --- 45 | usage: nand_dump_decoder.py [-h] -i INFOLDER -o OUTFILE [-c CONFIG] [-m MODE] [--atmel-config] [--nxp-fcb-config] 46 | 47 | optional arguments: 48 | -h, --help show this help message and exit 49 | -i INFOLDER, --infolder INFOLDER 50 | Input folder with binary dump files (.bin) 51 | -o OUTFILE, --outfile OUTFILE 52 | Output dump file 53 | -c CONFIG, --config CONFIG 54 | Configuration file 55 | -m MODE, --mode MODE Vendor specific NAND mode (ATMEL, NXP_IMX28 56 | --atmel-config Retrieve ATMEL config from first page of the dump file 57 | --nxp-fcb-config Retrieve NXP config from firmware control block (FCB) of first page of the dump file 58 | ``` 59 | 60 | The following output shows the help screen of the **NAND Dump Encoder**: 61 | 62 | ``` 63 | $ ./nand_dump_encoder.sh --help 64 | _ _ ___ _ _______ ______ _____ _ 65 | | \ | | / _ \ | \ | | _ \ | _ \ | ___| | | 66 | | \| |/ /_\ \| \| | | | | | | | |_ _ _ __ ___ _ __ | |__ _ __ ___ ___ __| | ___ _ __ 67 | | . ` || _ || . ` | | | | | | | | | | | '_ ` _ \| '_ \ | __| '_ \ / __/ _ \ / _` |/ _ \ '__| 68 | | |\ || | | || |\ | |/ / | |/ /| |_| | | | | | | |_) | | |__| | | | (_| (_) | (_| | __/ | 69 | \_| \_/\_| |_/\_| \_/___/ |___/ \__,_|_| |_| |_| .__/ \____/_| |_|\___\___/ \__,_|\___|_| 70 | | | 71 | |_| 72 | NAND Dump Encoder v0.2 by Matthias Deeg - SySS GmbH (c) 2018-2020 73 | --- 74 | usage: nand_dump_encoder.py [-h] -i INFILE -o OUTFILE [-c CONFIG] [--atmel-config] [-k KEY] 75 | 76 | optional arguments: 77 | -h, --help show this help message and exit 78 | -i INFILE, --infile INFILE 79 | Input file 80 | -o OUTFILE, --outfile OUTFILE 81 | Output dump file 82 | -c CONFIG, --config CONFIG 83 | Configuration file 84 | --atmel-config Use ATMEL config in first page of the dump file 85 | -k KEY, --key KEY Crypto key for ATMEL ECC encryption 86 | ``` 87 | 88 | ## Example 89 | 90 | The following example shows how the errors of a raw NAND flash memory dump of a [SAMA5D4 Xplained Ultra evaluation board](https://www.microchip.com/DevelopmentTools/ProductDetails/ATSAMA5D4-XULT) with a MT29F4G08 NAND flash memory chip (device ID 2CDC90A6) are corrected using the NAND Dump Decoder. 91 | The configuration of the ATMEL target system is read from the dump itself (PMECC header in first NAND dump block) using the command line argument **--atmel-config**. Alternatively, the command line argument **--config** can be used to specify a suitable NAND configuration. 92 | 93 | ``` 94 | $ ./nand_dump_decoder.sh -i ~/dump/SAMA5D4/dump1 -o sama.bin --atmel-config 95 | _ _ ___ _ _______ ______ ______ _ 96 | | \ | | / _ \ | \ | | _ \ | _ \ | _ \ | | 97 | | \| |/ /_\ \| \| | | | | | | | |_ _ _ __ ___ _ __ | | | |___ ___ ___ __| | ___ _ __ 98 | | . ` || _ || . ` | | | | | | | | | | | '_ ` _ \| '_ \ | | | / _ \/ __/ _ \ / _` |/ _ \ '__| 99 | | |\ || | | || |\ | |/ / | |/ /| |_| | | | | | | |_) | | |/ / __/ (_| (_) | (_| | __/ | 100 | \_| \_/\_| |_/\_| \_/___/ |___/ \__,_|_| |_| |_| .__/ |___/ \___|\___\___/ \__,_|\___|_| 101 | | | 102 | |_| 103 | NAND Dump Decoder v0.2 by Matthias Deeg - SySS GmbH (c) 2018-2020 104 | --- 105 | [*] Found one binary input file (566231040 bytes) 106 | [*] Using ECC mode ATMEL 107 | [*] Used configuration 108 | Block size: 262144 bytes (64 pages) 109 | Page size: 4096 bytes 110 | Sector size: 512 bytes 111 | Spare size: 224 bytes 112 | ECC offset: 120 bytes 113 | ECC errors: 8 errors per sector (max.) 114 | ECC bytes: 13 bytes per sector 115 | Use ECC: True 116 | File offset: 0x0 (skip 0 blocks) 117 | [*] Search for ECC crypto key ... 118 | [*] Found ECC crypto key: f78a7490b7c95943e99ea724ad 119 | [*] Starting error correcting process ... 120 | Progress: 100.00% (1048576/1048576 sectors) 121 | [*] Completed error correcting process 122 | Successfully written 536870912 bytes of data to output file 'sama.bin' 123 | ----- 124 | Some statistics 125 | Total pages: 131072 126 | Blank pages: 90846 (69.31%) 127 | Blank sectors: 726768 (69.31%) 128 | Data sectors: 321808 (30.69%) 129 | Total sectors: 1048576 130 | Valid sectors: 1048576 (100.00%) 131 | Valid data sectors: 321808 (30.69%) 132 | Corrupted sectors: 0 (0.00%) 133 | Corrected sectors: 2 (0.00%) 134 | Bad blocks: 0 135 | ``` 136 | 137 | Besides an error-corrected data dump, in this example, the NAND Dump Decoder also extracts a found cryptographic key which is used by some target systems for encrypting the error-correcting codes within the NAND spare areas, like in our SAMA5D4 device. 138 | 139 | The created error-corrected NAND dump contains only data of user areas and can be used with the Memory Technology Device (MTD) System for Linux and its NAND simulator in the following way: 140 | 141 | ``` 142 | modprobe nandsim first_id_byte=0x2C second_id_byte=0xdc third_id_byte=0x90 fourth_id_byte=0xa6 143 | 144 | cat /proc/mtd 145 | 146 | nandwrite /dev/mtd1 sama.bin 147 | ``` 148 | 149 | If the NAND flash memory dump contains an Unsorted Block Image (UBI) with a UBI file system (UBIFS), like in this example, it can be mounted as follows: 150 | 151 | ``` 152 | modprobe ubi mtd=/dev/mtd1,4096 153 | 154 | mkdir /tmp/nand_dump 155 | 156 | mount -t ubifs -o rw /dev/ubi0_0 /tmp/nand_dump 157 | ``` 158 | 159 | By using the software tool [ubireader_utils_info](https://github.com/jrspruitt/ubi_reader), the corresponding configuration of the Unsorted Block Image can be extracted, as the following output shows: 160 | 161 | ``` 162 | $ ubireader_utils_info sama.bin 163 | 164 | Volume rootfs 165 | alignment -a 1 166 | default_compr -x lzo 167 | fanout -f 8 168 | image_seq -Q 928361211 169 | key_hash -k r5 170 | leb_size -e 253952 171 | log_lebs -l 4 172 | max_bud_bytes -j 8388608 173 | max_leb_cnt -c 2082 174 | min_io_size -m 4096 175 | name -N rootfs 176 | orph_lebs -p 1 177 | peb_size -p 262144 178 | sub_page_size -s 4096 179 | version -x 1 180 | vid_hdr_offset -O 4096 181 | vol_id -n 0 182 | 183 | #ubinize.ini# 184 | [rootfs] 185 | vol_type=dynamic 186 | vol_flags=0 187 | vol_id=0 188 | vol_name=rootfs 189 | vol_alignment=1 190 | vol_size=500285440 191 | Writing to: ubifs-root/sama.bin/img-928361211/create_ubi_img-928361211.sh 192 | Writing to: ubifs-root/sama.bin/img-928361211/img-928361211.ini 193 | ``` 194 | 195 | With this information, an Unsorted Block Image with the correctly formatted UBIFS can be created in the following way: 196 | 197 | ``` 198 | mkfs.ubifs -m 4096 -e 253952 -c 2082 -x lzo -f 8 -k r5 -p 1 -l 4 -r /tmp/nand_dump/ sama_hacked.ubifs 199 | 200 | ubinize -p 262144 -m 4096 -O 4096 -s 4096 -x 1 -Q 928361211 -o sama_hacked.ubi sama_hacked.ini 201 | ``` 202 | 203 | Before such a created UBI file is further processed, its size should be adjusted via padding with 0xFF bytes to match the specification of the corresponding NAND memory chip, for instance 536870912 bytes for the 512 MB MT29F4G08. 204 | In doing so, there won't be any problems when programming the NAND chip with a universal programmer like the [UP-828P](https://www.teeltech.com/mobile-device-forensic-hardware/up-828-programmer/). 205 | 206 | Now, a valid NAND flash memory image can be created with the NAND Dump Encoder using a suitable configuration for the target system (SAMA5D4 in this example) and the correct cryptographic key for encrypting the error-correcting BCH codes within the NAND spare areas. 207 | 208 | ``` 209 | $ ./nand_dump_encoder.sh -i hacked_dump.bin -o hacked_sama_image.bin -c conf/29F4G08_Micron_ATMEL.conf -k f78a7490b7c95943e99ea724ad 210 | 211 | _ _ ___ _ _______ ______ _____ _ 212 | | \ | | / _ \ | \ | | _ \ | _ \ | ___| | | 213 | | \| |/ /_\ \| \| | | | | | | | |_ _ _ __ ___ _ __ | |__ _ __ ___ ___ __| | ___ _ __ 214 | | . ` || _ || . ` | | | | | | | | | | | '_ ` _ \| '_ \ | __| '_ \ / __/ _ \ / _` |/ _ \ '__| 215 | | |\ || | | || |\ | |/ / | |/ /| |_| | | | | | | |_) | | |__| | | | (_| (_) | (_| | __/ | 216 | \_| \_/\_| |_/\_| \_/___/ |___/ \__,_|_| |_| |_| .__/ \____/_| |_|\___\___/ \__,_|\___|_| 217 | | | 218 | |_| 219 | NAND Dump Encoder v0.2 by Matthias Deeg - SySS GmbH (c) 2018-2020 220 | --- 221 | [*] Found input file with a file size of 536870912 bytes 222 | [*] Read configuration file 'conf/29F4G08_Micron_ATMEL.conf' 223 | [*] Used configuration 224 | Block size: 262144 bytes (64 pages) 225 | Page size: 4096 bytes 226 | Sector size: 512 bytes 227 | Spare size: 224 bytes 228 | ECC offset: 120 bytes 229 | ECC errors: 8 errors per sector (max.) 230 | ECC bytes: 13 bytes per sector 231 | Use ECC: True 232 | [*] Generating output file ... 233 | Progress: 100.00% (1048576/1048576 sectors) 234 | [*] Completed error correcting process 235 | Successfully written 536870912 bytes of data to output file 'hacked_sama_image.bin' 236 | ----- 237 | Some statistics 238 | Total pages: 131072 239 | Blank pages: 92445 (70.53%) 240 | Blank sectors: 739560 (70.53%) 241 | Data sectors: 309016 (29.47%) 242 | Total sectors: 1048576 243 | Bad blocks: 0 244 | ``` 245 | 246 | ## Demo 247 | 248 | This demo video exemplarily shows a chip-off/chip-on attack for gaining unauthorized root access on a SAMA5D4 device ([SAMA5D4 Xplained Ultra evaluation board](https://www.microchip.com/DevelopmentTools/ProductDetails/ATSAMA5D4-XULT)) by exploiting the unencrypted NAND flash memory. 249 | 250 | [![SySS PoC Video: Exploiting the Obvious But Not the Trivial - Unencrypted NAND Flash Memory](/images/exploiting_unencrypted_nand_poc_video.jpg)](https://www.youtube.com/watch?v=eTtfRDMjgww "Exploiting the Obvious But Not the Trivial - Unencrypted NAND Flash Memory") 251 | 252 | ## To-do 253 | * improve speed ;-) 254 | 255 | ## References 256 | 257 | * [BCH codes](https://en.wikipedia.org/wiki/BCH_code), Wikipedia, 2020 258 | * [PMECC Reader and Decoder](https://www.mickaelwalter.fr/2018/06/08/dumping-a-slc-nand-flash-with-atmel-pmecc/), Mickaël Walter, 2018 259 | * [SAMA5D4 Xplained Ultra](https://www.microchip.com/DevelopmentTools/ProductDetails/ATSAMA5D4-XULT), Microchip 260 | * [UP-828P](https://www.teeltech.com/mobile-device-forensic-hardware/up-828-programmer/) 261 | 262 | ## Disclaimer 263 | 264 | Use at your own risk. Do not use without full consent of everyone involved. 265 | For educational purposes only. 266 | -------------------------------------------------------------------------------- /conf/29F2F108_Micron.conf: -------------------------------------------------------------------------------- 1 | [default] 2 | Mode = NXP_P1014 3 | DeviceName = MT29F2G08 4 | Manufacturer = Micron 5 | BlockSize = 64 6 | PageSize = 2048 7 | SectorSize = 512 8 | SpareAreaSize = 64 9 | SectorsPerPage = 4 10 | MetadataSize = 0 11 | UseECC = True 12 | ECC_Offset = 8 13 | ECC_Errors = 4 14 | ECC_Bytes_Per_Sector = 8 15 | ECC_Polynom = 0x201b 16 | File_Offset = 0x0 17 | -------------------------------------------------------------------------------- /conf/29F4G08_Micron_ATMEL.conf: -------------------------------------------------------------------------------- 1 | [default] 2 | Mode = ATMEL 3 | DeviceName = MT29F4G08 4 | Manufacturer = Micron 5 | BlockSize = 64 6 | PageSize = 4096 7 | SectorSize = 512 8 | SpareAreaSize = 224 9 | SectorsPerPage = 8 10 | MetadataSize = 0 11 | UseECC = True 12 | ECC_Offset = 120 13 | ECC_Errors = 8 14 | ECC_Bytes_Per_Sector = 13 15 | ECC_Polynom = 0x201b 16 | File_Offset = 0x0 17 | -------------------------------------------------------------------------------- /conf/K9F1G08U0D_Samsung.conf: -------------------------------------------------------------------------------- 1 | [default] 2 | Mode = YAFFS2 3 | DeviceName = K9F1G08U0D 4 | Manufacturer = Samsung 5 | BlockSize = 64 6 | PageSize = 2048 7 | SectorSize = 256 8 | SpareAreaSize = 64 9 | SectorsPerPage = 8 10 | MetadataSize = 0 11 | UseECC = True 12 | ECC_Offset = 0 13 | ECC_Errors = 1 14 | ECC_Bytes_Per_Sector = 3 15 | ECC_Polynom = 0x0 16 | File_Offset = 0x0 17 | -------------------------------------------------------------------------------- /conf/MX30LF2G18AC_NXP_IMX28.conf: -------------------------------------------------------------------------------- 1 | [default] 2 | Mode = NXP_IMX28 3 | DeviceName = MX30LF2G18AC 4 | Manufacturer = Macronix 5 | BlockSize = 64 6 | PageSize = 2048 7 | SectorSize = 512 8 | SpareAreaSize = 64 9 | SectorsPerPage = 4 10 | MetadataSize = 10 11 | UseECC = True 12 | ECC_Offset = 0 13 | ECC_Errors = 8 14 | ECC_Bytes_Per_Sector = 13 15 | ECC_Polynom = 0x201b 16 | File_Offset = 0x2100000 17 | -------------------------------------------------------------------------------- /conf/S34ML01G2_ATMEL.conf: -------------------------------------------------------------------------------- 1 | [default] 2 | Mode = ATMEL 3 | DeviceName = S34ML01G2 4 | Manufacturer = Spansion 5 | BlockSize = 64 6 | PageSize = 2048 7 | SectorSize = 512 8 | SpareAreaSize = 64 9 | SectorsPerPage = 4 10 | MetadataSize = 0 11 | UseECC = True 12 | ECC_Offset = 36 13 | ECC_Errors = 4 14 | ECC_Bytes_Per_Sector = 7 15 | ECC_Polynom = 0x201b 16 | File_Offset = 0x0 17 | -------------------------------------------------------------------------------- /images/exploiting_unencrypted_nand_poc_video.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SySS-Research/nand-dump-tools/153c797544f2aae5f1db9d1c49c0fc13e34a66d5/images/exploiting_unencrypted_nand_poc_video.jpg -------------------------------------------------------------------------------- /nand_dump_decoder.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | NAND Dump Decoder 6 | 7 | Simple software tool for decoding raw dumps of NAND memory chips using 8 | implemented error correcting codes (ECC) like BCH or Hamming codes 9 | by Matthias Deeg 10 | 11 | based on PMECC reader and decoder by Mickaël Walter 12 | https://www.mickaelwalter.fr/2018/06/08/dumping-a-slc-nand-flash-with-atmel-pmecc/ 13 | 14 | uses BCH library for Python (python-bchlib) by Jeff Kent 15 | https://github.com/jkent/python-bchlib 16 | 17 | MIT License 18 | 19 | Copyright (c) 2018-2020 SySS GmbH 20 | 21 | Permission is hereby granted, free of charge, to any person obtaining a copy 22 | of this software and associated documentation files (the "Software"), to deal 23 | in the Software without restriction, including without limitation the rights 24 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 25 | copies of the Software, and to permit persons to whom the Software is 26 | furnished to do so, subject to the following conditions: 27 | 28 | The above copyright notice and this permission notice shall be included in all 29 | copies or substantial portions of the Software. 30 | 31 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 32 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 33 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 34 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 35 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 36 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 37 | SOFTWARE. 38 | """ 39 | 40 | __version__ = '0.2.1' 41 | __author__ = 'Matthias Deeg, Moritz Lottermann' 42 | 43 | import argparse 44 | import configparser 45 | import bchlib 46 | import mmap 47 | import os 48 | import struct 49 | import sys 50 | 51 | from yaffs_ecc import yaffs_extract_ecc, yaffs_calc_ecc_256, yaffs_ecc_correct 52 | 53 | # BCH polynom 54 | ECC_POLY1 = 0x201b # 8219 55 | ECC_POLY2 = 0x4443 # 17475 56 | 57 | # binary dump file extension 58 | DUMP_FILE_EXTENSION = ".bin" 59 | 60 | 61 | def read_bits(value, low, high, bits=8): 62 | format_str = "0{:d}b".format(bits) 63 | 64 | n = format(value, format_str)[::-1] 65 | return int(n[low:high][::-1], 2) 66 | 67 | 68 | def reverse_bits(data): 69 | reversed_value = b'' 70 | for i in range(0, len(data)): 71 | reversed_value += bytes([int('{:08b}'.format(data[i])[::-1], 2)]) 72 | return reversed_value 73 | 74 | 75 | def xor_crypto(data, key): 76 | return bytes(a ^ b for a, b in zip(data, key)) 77 | 78 | 79 | def read_atmel_config(page, config): 80 | """Read ATMEL PMECC configuration from first page""" 81 | 82 | # read ATMEL PMECC header (52 times at the beginning of the first page) 83 | header = struct.unpack("= 0 and corrected[0] <= bch.t: 355 | # correctable number of errors 356 | 357 | if len(corrected[1]) == config['sectorsize']: 358 | # write corrected sector data to output file 359 | fout.write(reverse_bits(corrected[1])) 360 | 361 | # increment good sector count 362 | good_sector_count += 1 363 | 364 | # clear bad sector flag 365 | bad_sector = False 366 | 367 | # sector had no bit errors 368 | if corrected[0] == 0: 369 | uncorrected_sector_count += 1 370 | else: 371 | corrected_sector_count += 1 372 | 373 | # early exit if we have a good sector 374 | break 375 | 376 | else: 377 | print("[-] Error: Corrected data of sector {} has " 378 | "wrong size ({})" 379 | .format(sector, config['sectorsize'])) 380 | # set bad sector flag 381 | bad_sector = True 382 | else: 383 | # set bad sector flag 384 | bad_sector = True 385 | 386 | # check if the sector was corrupted in all input files 387 | if bad_sector: 388 | # write corrupted sector data to output file 389 | fout.write(reverse_bits(corrected[1])) 390 | 391 | # increment bad sector count 392 | bad_sector_count += 1 393 | 394 | # print("incorrectable:{} {} {}".format(block, page, sector)) 395 | 396 | # show some statistics during processing all sectors 397 | progress = processed_sector_count / total_sectors * 100 398 | print("\r Progress: {:.2f}% ({}/{} sectors)" 399 | .format(progress, processed_sector_count, total_sectors), end="") 400 | 401 | # close output file 402 | fout.close() 403 | 404 | # close memory-maps 405 | for mm in input_file_mmaps: 406 | mm.close() 407 | 408 | # close input files 409 | for f in input_file_handles: 410 | f.close() 411 | 412 | # show some statistics at the end 413 | good_sector_percentage = good_sector_count / total_sectors * 100 414 | bad_sector_percentage = bad_sector_count / total_sectors * 100 415 | corrected_sector_percentage = corrected_sector_count / total_sectors * 100 416 | blank_page_percentage = blank_page_count / total_page_count * 100 417 | blank_sector_count = blank_page_count * sectors_per_page 418 | blank_sector_percentage = blank_sector_count / total_sectors * 100 419 | good_data_sector_count = good_sector_count - blank_sector_count 420 | good_data_sector_percentage = good_data_sector_count / total_sectors * 100 421 | data_sector_count = good_data_sector_count + bad_sector_count 422 | data_sector_percentage = data_sector_count / total_sectors * 100 423 | 424 | print("\n[*] Completed error correcting process") 425 | print(" Successfully written {} bytes of data to output file '{}'" 426 | .format(config['sectorsize'] * total_sectors, outfile)) 427 | print(" -----\n Some statistics\n" 428 | " Total pages: {}\n" 429 | " Blank pages: {} ({:.2f}%)\n" 430 | " Blank sectors: {} ({:.2f}%)\n" 431 | " Data sectors: {} ({:.2f}%)\n" 432 | " Total sectors: {}\n" 433 | " Valid sectors: {} ({:.2f}%)\n" 434 | " Valid data sectors: {} ({:.2f}%)\n" 435 | " Corrupted sectors: {} ({:.2f}%)\n" 436 | " Corrected sectors: {} ({:.2f}%)\n" 437 | " Bad blocks: {}" 438 | .format(total_page_count, blank_page_count, blank_page_percentage, 439 | blank_sector_count, blank_sector_percentage, 440 | data_sector_count, data_sector_percentage, 441 | total_sectors, good_sector_count, good_sector_percentage, 442 | good_data_sector_count, good_data_sector_percentage, 443 | bad_sector_count, bad_sector_percentage, 444 | corrected_sector_count, corrected_sector_percentage, 445 | bad_block_count)) 446 | 447 | 448 | def read_nxp_imx28_config(page, config): 449 | """Read NXP firmware control block (FCB) configuration from first page 450 | 451 | FCB is described in section 12.12.1.13 of the i.MX28 Reference Manual 452 | http://cache.freescale.com/files/dsp/doc/ref_manual/MCIMX28RM.pdf 453 | """ 454 | 455 | # read FCB 456 | fcb = page[12:12 + 512] 457 | 458 | # read FCB structure (from data sheet MCIMX28RM) 459 | checksum = struct.unpack("= 0 and corrected[0] <= bch.t: 638 | # correctable number of errors 639 | 640 | if len(corrected[1]) == expected_size: 641 | # write corrected sector data to output file 642 | # skip metadata for first sector in page 643 | fout.write(reverse_bits(corrected[1][config['metadata_size']:])) 644 | 645 | # increment good sector count 646 | good_sector_count += 1 647 | 648 | # clear bad sector flag 649 | bad_sector = False 650 | 651 | # sector had no bit errors 652 | if corrected[0] == 0: 653 | uncorrected_sector_count += 1 654 | else: 655 | corrected_sector_count += 1 656 | 657 | # early exit if we have a good sector 658 | break 659 | 660 | else: 661 | print("[-] Error: Corrected data of sector {} has " 662 | "wrong size ({})" 663 | .format(sector, config['sectorsize'])) 664 | # set bad sector flag 665 | bad_sector = True 666 | else: 667 | # set bad sector flag 668 | bad_sector = True 669 | 670 | # check if the sector was corrupted in all input files 671 | if bad_sector: 672 | # write corrupted sector data to output file 673 | fout.write(reverse_bits(corrected[1])) 674 | 675 | # increment bad sector count 676 | bad_sector_count += 1 677 | 678 | # print("incorrectable:{} {} {}".format(block, page, sector)) 679 | 680 | # process remaining sectors 681 | for sector in range(1, sectors_per_page): 682 | # initialize bad sector flag 683 | bad_sector = False 684 | 685 | # increment count of processed sectors 686 | processed_sector_count += 1 687 | 688 | # use all input files, if required (early exit condition) 689 | for mmin in input_file_mmaps: 690 | # read page of current memory-map 691 | start_page = start_block + page * config['fullpagesize'] 692 | end_page = start_page + config['fullpagesize'] 693 | page_data = mmin[start_page:end_page] 694 | 695 | # get data of current sector 696 | start_data = sector * config['sectorsize'] + \ 697 | config['metadata_size'] + \ 698 | sector * int(config['ecc_bytes_per_sector']) 699 | end_data = start_data + config['sectorsize'] 700 | sector_data = reverse_bits(page_data[start_data:end_data]) 701 | 702 | # get ECC of current sector 703 | start_ecc = start_data + config['sectorsize'] 704 | end_ecc = start_ecc + int(config['ecc_bytes_per_sector']) 705 | sector_ecc = reverse_bits(page_data[start_ecc:end_ecc]) 706 | 707 | # # calculate ECC 708 | # calc_ecc = bch.encode(sector_data) 709 | 710 | # apply BCH error correcting code 711 | corrected = bch.decode(sector_data, sector_ecc) 712 | 713 | if corrected[0] >= 0 and corrected[0] <= bch.t: 714 | # correctable number of errors 715 | 716 | if len(corrected[1]) == config['sectorsize']: 717 | # write corrected sector data to output file 718 | fout.write(reverse_bits(corrected[1])) 719 | 720 | # increment good sector count 721 | good_sector_count += 1 722 | 723 | # clear bad sector flag 724 | bad_sector = False 725 | 726 | # sector had no bit errors 727 | if corrected[0] == 0: 728 | uncorrected_sector_count += 1 729 | else: 730 | corrected_sector_count += 1 731 | 732 | # early exit if we have a good sector 733 | break 734 | 735 | else: 736 | print("[-] Error: Corrected data of sector {} has " 737 | "wrong size ({})" 738 | .format(sector, config['sectorsize'])) 739 | # set bad sector flag 740 | bad_sector = True 741 | else: 742 | # set bad sector flag 743 | bad_sector = True 744 | 745 | # check if the sector was corrupted in all input files 746 | if bad_sector: 747 | # write corrupted sector data to output file 748 | fout.write(reverse_bits(corrected[1])) 749 | 750 | # increment bad sector count 751 | bad_sector_count += 1 752 | 753 | # print("incorrectable:{} {} {}".format(block, page, sector)) 754 | 755 | # show some statistics during processing all sectors 756 | progress = processed_sector_count / total_sectors * 100 757 | print("\r Progress: {:.2f}% ({}/{} sectors)" 758 | .format(progress, processed_sector_count, total_sectors), end="") 759 | 760 | # close output file 761 | fout.close() 762 | 763 | # close memory-maps 764 | for mm in input_file_mmaps: 765 | mm.close() 766 | 767 | # close input files 768 | for f in input_file_handles: 769 | f.close() 770 | 771 | # show some statistics at the end 772 | good_sector_percentage = good_sector_count / total_sectors * 100 773 | bad_sector_percentage = bad_sector_count / total_sectors * 100 774 | corrected_sector_percentage = corrected_sector_count / total_sectors * 100 775 | blank_page_percentage = blank_page_count / total_page_count * 100 776 | blank_sector_count = blank_page_count * sectors_per_page 777 | blank_sector_percentage = blank_sector_count / total_sectors * 100 778 | good_data_sector_count = good_sector_count - blank_sector_count 779 | good_data_sector_percentage = good_data_sector_count / total_sectors * 100 780 | data_sector_count = good_data_sector_count + bad_sector_count 781 | data_sector_percentage = data_sector_count / total_sectors * 100 782 | 783 | print("\n[*] Completed error correcting process") 784 | print(" Successfully written {} bytes of data to output file '{}'" 785 | .format(config['sectorsize'] * total_sectors, outfile)) 786 | print(" -----\n Some statistics\n" 787 | " Total pages: {}\n" 788 | " Blank pages: {} ({:.2f}%)\n" 789 | " Blank sectors: {} ({:.2f}%)\n" 790 | " Data sectors: {} ({:.2f}%)\n" 791 | " Total sectors: {}\n" 792 | " Valid sectors: {} ({:.2f}%)\n" 793 | " Valid data sectors: {} ({:.2f}%)\n" 794 | " Corrupted sectors: {} ({:.2f}%)\n" 795 | " Corrected sectors: {} ({:.2f}%)\n" 796 | " Bad blocks: {}" 797 | .format(total_page_count, blank_page_count, blank_page_percentage, 798 | blank_sector_count, blank_sector_percentage, 799 | data_sector_count, data_sector_percentage, 800 | total_sectors, good_sector_count, good_sector_percentage, 801 | good_data_sector_count, good_data_sector_percentage, 802 | bad_sector_count, bad_sector_percentage, 803 | corrected_sector_count, corrected_sector_percentage, 804 | bad_block_count)) 805 | 806 | return 807 | 808 | 809 | def nxp_p1014_error_correction(infiles, outfile, config): 810 | """Do some error correction""" 811 | 812 | # initialize BCH decoder 813 | bch = bchlib.BCH(config['ecc_polynom'], config['ecc_errors'], False) 814 | 815 | # open output file 816 | fout = open(outfile, "wb") 817 | 818 | # initialize some variables 819 | processed_sector_count = 0 820 | corrected_sector_count = 0 821 | uncorrected_sector_count = 0 822 | good_sector_count = 0 823 | bad_sector_count = 0 824 | blank_page_count = 0 825 | bad_block_count = 0 826 | total_page_count = config['filesize'] // config['fullpagesize'] 827 | sectors_per_page = config['pagesize'] // config['sectorsize'] 828 | total_sectors = total_page_count * sectors_per_page 829 | page_size = config['pagesize'] 830 | block_size_bytes = config['fullpagesize'] * config['blocksize'] 831 | total_blocks = config['filesize'] // block_size_bytes 832 | 833 | # blank page data 834 | blank_page = b'\xff' * config['fullpagesize'] 835 | 836 | # work with input files 837 | input_file_handles = [] 838 | input_file_index = 0 839 | for f in infiles: 840 | input_file_handles.append(open(f, "r+b")) 841 | 842 | # memory-map the input files 843 | input_file_mmaps = [] 844 | for fin in input_file_handles: 845 | input_file_mmaps.append(mmap.mmap(fin.fileno(), 0)) 846 | 847 | # set current input file memory-map 848 | mm = input_file_mmaps[input_file_index] 849 | 850 | # with open(infile, "rb") as fin: 851 | print("[*] Starting error correcting process ...") 852 | 853 | for block in range(total_blocks): 854 | # read current block data 855 | # fin.seek(block * block_size_bytes) 856 | # block_data = fin.read(block_size_bytes) 857 | 858 | start_block = block * block_size_bytes 859 | end_block = start_block + block_size_bytes 860 | block_data = mm[start_block:end_block] 861 | 862 | # process pages in block 863 | for page in range(config['blocksize']): 864 | start_page = page * config['fullpagesize'] 865 | end_page = start_page + config['fullpagesize'] 866 | page_data = block_data[start_page:end_page] 867 | 868 | # check if page is blank 869 | if page_data == blank_page: 870 | # increment blank page counter 871 | blank_page_count += 1 872 | 873 | # increment good sector counter 874 | good_sector_count += sectors_per_page 875 | 876 | # increment count of processed sectors 877 | processed_sector_count += sectors_per_page 878 | 879 | # write blank page to output file 880 | fout.write(blank_page[:page_size]) 881 | 882 | # show some statistics during processing all sectors 883 | progress = processed_sector_count / total_sectors * 100 884 | print("\r Progress: {:.2f}% ({}/{} sectors)" 885 | .format(progress, processed_sector_count, 886 | total_sectors), end="") 887 | continue 888 | 889 | # process sectors in page 890 | for sector in range(sectors_per_page): 891 | #input() 892 | # initialize bad sector flag 893 | bad_sector = False 894 | 895 | # increment count of processed sectors 896 | processed_sector_count += 1 897 | 898 | # use all input files, if required (early exit condition) 899 | for mmin in input_file_mmaps: 900 | # read page of current memory-map 901 | start_page = start_block + page * config['fullpagesize'] 902 | end_page = start_page + config['fullpagesize'] 903 | page_data = mmin[start_page:end_page] 904 | 905 | # get ECC of current sector 906 | start_ecc = config['pagesize'] + config['ecc_offset'] + \ 907 | config['ecc_bytes_per_sector'] * sector 908 | end_ecc = start_ecc + config['ecc_bytes_per_sector'] -1 909 | sector_ecc = page_data[start_ecc:end_ecc] 910 | 911 | # get data of current sector 912 | start_data = sector * config['sectorsize'] 913 | end_data = start_data + config['sectorsize'] 914 | sector_data = page_data[start_data:end_data] 915 | 916 | # calculate ECC 917 | # calc_ecc = reverse_bits(bch.encode(sector_data)) 918 | 919 | # apply BCH error correcting code 920 | #print(len(sector_ecc)) 921 | corrected = bch.decode(sector_data, sector_ecc) 922 | #print(corrected[0]) 923 | if corrected[0] >= 0 and corrected[0] <= bch.t: 924 | # correctable number of errors 925 | 926 | if len(corrected[1]) == config['sectorsize']: 927 | # write corrected sector data to output file 928 | fout.write(corrected[1]) 929 | 930 | # increment good sector count 931 | good_sector_count += 1 932 | 933 | # clear bad sector flag 934 | bad_sector = False 935 | 936 | # sector had no bit errors 937 | if corrected[0] == 0: 938 | uncorrected_sector_count += 1 939 | else: 940 | corrected_sector_count += 1 941 | 942 | # early exit if we have a good sector 943 | break 944 | 945 | else: 946 | print("[-] Error: Corrected data of sector {} has " 947 | "wrong size ({})" 948 | .format(sector, config['sectorsize'])) 949 | # set bad sector flag 950 | bad_sector = True 951 | else: 952 | # set bad sector flag 953 | bad_sector = True 954 | 955 | # check if the sector was corrupted in all input files 956 | if bad_sector: 957 | # write corrupted sector data to output file 958 | fout.write(reverse_bits(corrected[1])) 959 | 960 | # increment bad sector count 961 | bad_sector_count += 1 962 | 963 | # print("incorrectable:{} {} {}".format(block, page, sector)) 964 | 965 | # show some statistics during processing all sectors 966 | progress = processed_sector_count / total_sectors * 100 967 | print("\r Progress: {:.2f}% ({}/{} sectors)" 968 | .format(progress, processed_sector_count, total_sectors), end="") 969 | 970 | # close output file 971 | fout.close() 972 | 973 | # close memory-maps 974 | for mm in input_file_mmaps: 975 | mm.close() 976 | 977 | # close input files 978 | for f in input_file_handles: 979 | f.close() 980 | 981 | # show some statistics at the end 982 | good_sector_percentage = good_sector_count / total_sectors * 100 983 | bad_sector_percentage = bad_sector_count / total_sectors * 100 984 | corrected_sector_percentage = corrected_sector_count / total_sectors * 100 985 | blank_page_percentage = blank_page_count / total_page_count * 100 986 | blank_sector_count = blank_page_count * sectors_per_page 987 | blank_sector_percentage = blank_sector_count / total_sectors * 100 988 | good_data_sector_count = good_sector_count - blank_sector_count 989 | good_data_sector_percentage = good_data_sector_count / total_sectors * 100 990 | data_sector_count = good_data_sector_count + bad_sector_count 991 | data_sector_percentage = data_sector_count / total_sectors * 100 992 | 993 | print("\n[*] Completed error correcting process") 994 | print(" Successfully written {} bytes of data to output file '{}'" 995 | .format(config['sectorsize'] * total_sectors, outfile)) 996 | print(" -----\n Some statistics\n" 997 | " Total pages: {}\n" 998 | " Blank pages: {} ({:.2f}%)\n" 999 | " Blank sectors: {} ({:.2f}%)\n" 1000 | " Data sectors: {} ({:.2f}%)\n" 1001 | " Total sectors: {}\n" 1002 | " Valid sectors: {} ({:.2f}%)\n" 1003 | " Valid data sectors: {} ({:.2f}%)\n" 1004 | " Corrupted sectors: {} ({:.2f}%)\n" 1005 | " Corrected sectors: {} ({:.2f}%)" 1006 | .format(total_page_count, blank_page_count, blank_page_percentage, 1007 | blank_sector_count, blank_sector_percentage, 1008 | data_sector_count, data_sector_percentage, 1009 | total_sectors, good_sector_count, good_sector_percentage, 1010 | good_data_sector_count, good_data_sector_percentage, 1011 | bad_sector_count, bad_sector_percentage, 1012 | corrected_sector_count, corrected_sector_percentage)) 1013 | 1014 | 1015 | def yaffs_error_correction(infiles, outfile, config): 1016 | """Do some error correction""" 1017 | 1018 | # open output file 1019 | fout = open(outfile, "wb") 1020 | 1021 | # initialize some variables 1022 | processed_sector_count = 0 1023 | corrected_sector_count = 0 1024 | uncorrected_sector_count = 0 1025 | good_sector_count = 0 1026 | bad_sector_count = 0 1027 | blank_page_count = 0 1028 | bad_block_count = 0 1029 | total_page_count = config['filesize'] // config['fullpagesize'] 1030 | sectors_per_page = config['pagesize'] // config['sectorsize'] 1031 | total_sectors = total_page_count * sectors_per_page 1032 | page_size = config['pagesize'] 1033 | block_size_bytes = config['fullpagesize'] * config['blocksize'] 1034 | total_blocks = config['filesize'] // block_size_bytes 1035 | 1036 | # blank page data 1037 | blank_page = b'\xff' * config['fullpagesize'] 1038 | 1039 | # bad block offsets (YAFFS2) 1040 | # bb_offset1 = config['pagesize'] 1041 | 1042 | # work with input files 1043 | input_file_handles = [] 1044 | input_file_index = 0 1045 | for f in infiles: 1046 | input_file_handles.append(open(f, "r+b")) 1047 | 1048 | # memory-map the input files 1049 | input_file_mmaps = [] 1050 | for fin in input_file_handles: 1051 | input_file_mmaps.append(mmap.mmap(fin.fileno(), 0)) 1052 | 1053 | # set current input file memory-map 1054 | mm = input_file_mmaps[input_file_index] 1055 | 1056 | # with open(infile, "rb") as fin: 1057 | print("[*] Starting error correcting process ...") 1058 | 1059 | for block in range(total_blocks): 1060 | # read current block data 1061 | # fin.seek(block * block_size_bytes) 1062 | # block_data = fin.read(block_size_bytes) 1063 | 1064 | start_block = block * block_size_bytes 1065 | end_block = start_block + block_size_bytes 1066 | block_data = mm[start_block:end_block] 1067 | 1068 | # bad block logic for YAFFS2 has to be added 1069 | 1070 | # 1st page bad block marker 1071 | # b1 = block_data[bb_offset1] 1072 | 1073 | # if b1 != 0xff: 1074 | # increment bad block counter 1075 | # bad_block_count += 1 1076 | # continue 1077 | 1078 | # process pages in block 1079 | for page in range(config['blocksize']): 1080 | start_page = page * config['fullpagesize'] 1081 | end_page = start_page + config['fullpagesize'] 1082 | page_data = block_data[start_page:end_page] 1083 | 1084 | # extract ECCs for all sectors from spare area 1085 | read_ecc = yaffs_extract_ecc(page_data[config['pagesize']:], 1086 | config['ecc_bytes_per_sector'], 1087 | sectors_per_page) 1088 | 1089 | # check if page is blank 1090 | if page_data == blank_page or len(read_ecc) == 0: 1091 | # increment blank page counter 1092 | blank_page_count += 1 1093 | 1094 | # increment good sector counter 1095 | good_sector_count += sectors_per_page 1096 | 1097 | # increment count of processed sectors 1098 | processed_sector_count += sectors_per_page 1099 | 1100 | # write blank page to output file 1101 | fout.write(blank_page[:page_size]) 1102 | 1103 | # show some statistics during processing all sectors 1104 | progress = processed_sector_count / total_sectors * 100 1105 | print("\r Progress: {:.2f}% ({}/{} sectors)" 1106 | .format(progress, processed_sector_count, 1107 | total_sectors), end="") 1108 | continue 1109 | 1110 | # process sectors in page 1111 | for sector in range(sectors_per_page): 1112 | # initialize bad sector flag 1113 | bad_sector = False 1114 | 1115 | # increment count of processed sectors 1116 | processed_sector_count += 1 1117 | 1118 | # use all input files, if required (early exit condition) 1119 | for mmin in input_file_mmaps: 1120 | # read page of current memory-map 1121 | start_page = start_block + page * config['fullpagesize'] 1122 | end_page = start_page + config['fullpagesize'] 1123 | page_data = mmin[start_page:end_page] 1124 | 1125 | # get data of current sector 1126 | start_data = sector * config['sectorsize'] 1127 | end_data = start_data + config['sectorsize'] 1128 | sector_data = page_data[start_data:end_data] 1129 | 1130 | # calculate ECC 1131 | test_ecc = yaffs_calc_ecc_256(sector_data) 1132 | corrected = yaffs_ecc_correct(sector_data, read_ecc[sector], test_ecc) 1133 | 1134 | if corrected[0] == 1: 1135 | # correctable single bit error 1136 | corrected_sector_count += 1 1137 | 1138 | # write corrected sector data to output file 1139 | fout.write(bytes(corrected[1])) 1140 | 1141 | # increment good sector count 1142 | good_sector_count += 1 1143 | 1144 | # clear bad sector flag 1145 | bad_sector = False 1146 | 1147 | # early exit if we have a good sector 1148 | break 1149 | 1150 | elif corrected[0] == 0: 1151 | # write corrected sector data to output file 1152 | fout.write(bytes(corrected[1])) 1153 | 1154 | # increment good sector count 1155 | good_sector_count += 1 1156 | 1157 | # clear bad sector flag 1158 | bad_sector = False 1159 | 1160 | # early exit if we have a good sector 1161 | break 1162 | 1163 | else: 1164 | # set bad sector flag 1165 | bad_sector = True 1166 | 1167 | # increment uncorrected sector count 1168 | uncorrected_sector_count += 1 1169 | 1170 | # check if the sector was corrupted in all input files 1171 | if bad_sector: 1172 | # write corrupted sector data to output file 1173 | fout.write(corrected[1]) 1174 | 1175 | # increment bad sector count 1176 | bad_sector_count += 1 1177 | 1178 | # show some statistics during processing all sectors 1179 | progress = processed_sector_count / total_sectors * 100 1180 | print("\r Progress: {:.2f}% ({}/{} sectors)" 1181 | .format(progress, processed_sector_count, total_sectors), end="") 1182 | 1183 | # close output file 1184 | fout.close() 1185 | 1186 | # close memory-maps 1187 | for mm in input_file_mmaps: 1188 | mm.close() 1189 | 1190 | # close input files 1191 | for f in input_file_handles: 1192 | f.close() 1193 | 1194 | # show some statistics at the end 1195 | good_sector_percentage = good_sector_count / total_sectors * 100 1196 | bad_sector_percentage = bad_sector_count / total_sectors * 100 1197 | corrected_sector_percentage = corrected_sector_count / total_sectors * 100 1198 | blank_page_percentage = blank_page_count / total_page_count * 100 1199 | blank_sector_count = blank_page_count * sectors_per_page 1200 | blank_sector_percentage = blank_sector_count / total_sectors * 100 1201 | good_data_sector_count = good_sector_count - blank_sector_count 1202 | good_data_sector_percentage = good_data_sector_count / total_sectors * 100 1203 | data_sector_count = good_data_sector_count + bad_sector_count 1204 | data_sector_percentage = data_sector_count / total_sectors * 100 1205 | 1206 | print("\n[*] Completed error correcting process") 1207 | print(" Successfully written {} bytes of data to output file '{}'" 1208 | .format(config['sectorsize'] * total_sectors, outfile)) 1209 | print(" -----\n Some statistics\n" 1210 | " Total pages: {}\n" 1211 | " Blank pages: {} ({:.2f}%)\n" 1212 | " Blank sectors: {} ({:.2f}%)\n" 1213 | " Data sectors: {} ({:.2f}%)\n" 1214 | " Total sectors: {}\n" 1215 | " Valid sectors: {} ({:.2f}%)\n" 1216 | " Valid data sectors: {} ({:.2f}%)\n" 1217 | " Corrupted sectors: {} ({:.2f}%)\n" 1218 | " Corrected sectors: {} ({:.2f}%)\n" 1219 | " Bad blocks: {}" 1220 | .format(total_page_count, blank_page_count, blank_page_percentage, 1221 | blank_sector_count, blank_sector_percentage, 1222 | data_sector_count, data_sector_percentage, 1223 | total_sectors, good_sector_count, good_sector_percentage, 1224 | good_data_sector_count, good_data_sector_percentage, 1225 | bad_sector_count, bad_sector_percentage, 1226 | corrected_sector_count, corrected_sector_percentage, 1227 | bad_block_count)) 1228 | 1229 | 1230 | def show_config(config): 1231 | """Show configuration""" 1232 | 1233 | block_offset = config['file_offset'] // (config['blocksize'] * 1234 | config['fullpagesize']) 1235 | 1236 | print("[*] Used configuration\n" 1237 | " Block size: {} bytes ({} pages)\n" 1238 | " Page size: {} bytes\n" 1239 | " Sector size: {} bytes\n" 1240 | " Spare size: {} bytes\n" 1241 | " ECC offset: {} bytes\n" 1242 | " ECC errors: {} errors per sector (max.)\n" 1243 | " ECC bytes: {} bytes per sector\n" 1244 | " Use ECC: {}\n" 1245 | " File offset: 0x{:X} (skip {} blocks)" 1246 | .format(config['blocksize'] * config['pagesize'], 1247 | config['blocksize'], config['pagesize'], 1248 | config['sectorsize'], config['spareareasize'], 1249 | config['ecc_offset'], config['ecc_errors'], 1250 | config['ecc_bytes_per_sector'], config['useecc'], 1251 | config['file_offset'], block_offset)) 1252 | 1253 | 1254 | def banner(): 1255 | """Show a fancy banner""" 1256 | 1257 | print( 1258 | """ _ _ ___ _ _______ ______ ______ _ \n""" 1259 | """| \ | | / _ \ | \ | | _ \ | _ \ | _ \ | | \n""" 1260 | """| \| |/ /_\ \| \| | | | | | | | |_ _ _ __ ___ _ __ | | | |___ ___ ___ __| | ___ _ __ \n""" 1261 | """| . ` || _ || . ` | | | | | | | | | | | '_ ` _ \| '_ \ | | | / _ \/ __/ _ \ / _` |/ _ \ '__|\n""" 1262 | """| |\ || | | || |\ | |/ / | |/ /| |_| | | | | | | |_) | | |/ / __/ (_| (_) | (_| | __/ | \n""" 1263 | """\_| \_/\_| |_/\_| \_/___/ |___/ \__,_|_| |_| |_| .__/ |___/ \___|\___\___/ \__,_|\___|_| \n""" 1264 | """ | | \n""" 1265 | """ |_| \n""" 1266 | """NAND Dump Decoder v{0} by Matthias Deeg - SySS GmbH (c) 2018-2020\n---""".format(__version__)) 1267 | 1268 | 1269 | # supported vendor specific NAND layout with specific ECC algorithm 1270 | NAND_LAYOUT = { 1271 | "ATMEL": atmel_error_correction, 1272 | "NXP_IMX28": nxp_imx28_error_correction, 1273 | "NXP_P1014": nxp_p1014_error_correction, 1274 | "YAFFS2": yaffs_error_correction # experimental support 1275 | } 1276 | 1277 | 1278 | # main program 1279 | if __name__ == '__main__': 1280 | # show banner 1281 | banner() 1282 | 1283 | # init argument parser 1284 | parser = argparse.ArgumentParser() 1285 | parser.add_argument('-i', '--infolder', type=str, help='Input folder with binary dump files (.bin)', required=True) 1286 | parser.add_argument('-o', '--outfile', type=str, help='Output dump file', required=True) 1287 | parser.add_argument('-c', '--config', type=str, help='Configuration file') 1288 | parser.add_argument('-m', '--mode', type=str, help='Vendor specific NAND mode (ATMEL, NXP_IMX28, NXP_P1014, YAFFS2 [experimental])') 1289 | parser.add_argument('--atmel-config', action="store_true", help='Retrieve ATMEL config from first page of the dump file') 1290 | parser.add_argument('--nxp-fcb-config', action="store_true", help='Retrieve NXP config from firmware control block (FCB) of first page of the dump file') 1291 | 1292 | # parse arguments 1293 | args = parser.parse_args() 1294 | 1295 | # create empty configuration 1296 | config = {} 1297 | 1298 | # check if input folder contains binary dump files (.bin) 1299 | input_files = [] 1300 | for e in os.listdir(args.infolder): 1301 | if os.path.splitext(e)[1] == DUMP_FILE_EXTENSION: 1302 | input_files.append(os.path.normpath("{}/{}" 1303 | .format(args.infolder, e))) 1304 | 1305 | if len(input_files) == 0: 1306 | print("[-] The input folder does not contain any binary dump files (.bin)") 1307 | 1308 | # only process process input files of the same size 1309 | # the first binary file in the folder is our reference file 1310 | infile_size = os.path.getsize(input_files[0]) 1311 | config['filesize'] = infile_size 1312 | for f in input_files[1:]: 1313 | file_size = os.path.getsize(f) 1314 | 1315 | if file_size != infile_size: 1316 | # remove file from list if the size does not match 1317 | input_files.remove(f) 1318 | 1319 | if len(input_files) == 1: 1320 | print("[*] Found one binary input file ({} bytes)" 1321 | .format(config['filesize'])) 1322 | else: 1323 | print("[*] Found {} binary input files of the same size ({} bytes)" 1324 | .format(len(input_files), config['filesize'])) 1325 | 1326 | # check if ATMEL configuration within NAND dump should be used 1327 | if args.atmel_config: 1328 | # set some default ATMEL configuration parameters 1329 | config['mode'] = "ATMEL" 1330 | config['blocksize'] = 64 1331 | # config['pagesize'] = 2048 1332 | config['sectorsize'] = 512 1333 | config['spareareasize'] = 64 1334 | config['ecc_errors'] = 4 1335 | config['ecc_polynom'] = ECC_POLY1 1336 | config['file_offset'] = 0 1337 | 1338 | # try to identify page size (usually 2048 or 4096) 1339 | with open(input_files[0], "rb") as f: 1340 | data = f.read(2048 * 8) 1341 | 1342 | # set invalid page saze 1343 | config['pagesize'] = 0 1344 | 1345 | # use a simple check for 0xff bytes in spare area to determine 1346 | # the used page size 1347 | for ps in [512, 1024, 2048, 4096, 8192]: 1348 | if (data[ps] == 0xff) and (data[ps + 1] == 0xff): 1349 | config['pagesize'] = ps 1350 | 1351 | if config['pagesize'] == 0: 1352 | print("[-] Error: Could not determine used page size.\n" 1353 | "Please use a correct config file for this NAND memory dump.") 1354 | sys.exit(1) 1355 | 1356 | config['fullpagesize'] = config['pagesize'] + config['spareareasize'] 1357 | 1358 | # read ATMEL PMECC configuration from first page of first input file 1359 | with open(input_files[0], "rb") as f: 1360 | first_page = f.read(config['fullpagesize']) 1361 | config = read_atmel_config(first_page, config) 1362 | elif args.nxp_fcb_config: 1363 | # set some default NXP i.MX28 configuration parameters 1364 | config['mode'] = "NXP_IMX28" 1365 | config['file_offset'] = 0x2100000 1366 | # config['file_offset'] = 0x108000 1367 | config['ecc_polynom'] = ECC_POLY1 1368 | 1369 | # read NXP firmware control block (FCB) first page of first input file 1370 | with open(input_files[0], "rb") as f: 1371 | first_page = f.read(2048) 1372 | config = read_nxp_imx28_config(first_page, config) 1373 | 1374 | else: 1375 | # read configuration from given config file 1376 | if not os.path.isfile(args.config): 1377 | print("[-] Error: Config file '{}' does not exist".format(args.config)) 1378 | sys.exit(1) 1379 | 1380 | print("[*] Read configuration file '{}'".format(args.config)) 1381 | configfile = configparser.ConfigParser() 1382 | try: 1383 | configfile.read(args.config) 1384 | 1385 | # convert data types of parsed config data 1386 | config['mode'] = configfile['default']['mode'] 1387 | config['blocksize'] = int(configfile['default']['blocksize']) 1388 | config['pagesize'] = int(configfile['default']['pagesize']) 1389 | config['sectorsize'] = int(configfile['default']['sectorsize']) 1390 | config['spareareasize'] = int(configfile['default']['spareareasize']) 1391 | config['useecc'] = bool(configfile['default']['useecc']) 1392 | config['ecc_offset'] = int(configfile['default']['ecc_offset']) 1393 | config['ecc_errors'] = int(configfile['default']['ecc_errors']) 1394 | config['ecc_polynom'] = int(configfile['default']['ecc_polynom'], 16) 1395 | config['ecc_errors'] = int(configfile['default']['ecc_errors']) 1396 | config['ecc_bytes_per_sector'] = int(configfile['default']['ecc_bytes_per_sector']) 1397 | config['file_offset'] = int(configfile['default']['file_offset'], 16) 1398 | config['sectors_per_page'] = int(configfile['default']['sectorsperpage']) 1399 | config['metadata_size'] = int(configfile['default']['metadatasize']) 1400 | 1401 | except KeyError: 1402 | print("[-] Error: Could not read all required configuration values") 1403 | sys.exit(1) 1404 | 1405 | # check ECC mode 1406 | if config['mode'] in NAND_LAYOUT.keys(): 1407 | print("[*] Using ECC mode {}".format(config['mode'])) 1408 | else: 1409 | print(args) 1410 | if args.mode.upper() not in NAND_LAYOUT.keys(): 1411 | print("[-] Error: ECC mode is not set") 1412 | sys.exit(1) 1413 | 1414 | # set ECC mode 1415 | config['mode'] = args.mode.upper() 1416 | 1417 | # add derivated configuration parameters 1418 | config['fullpagesize'] = config['pagesize'] + config['spareareasize'] 1419 | 1420 | # show config 1421 | show_config(config) 1422 | 1423 | if config['useecc']: 1424 | ecc_method = NAND_LAYOUT[config['mode']] 1425 | ecc_method(input_files, args.outfile, config) 1426 | -------------------------------------------------------------------------------- /nand_dump_decoder.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | script_dir=$(cd $(dirname $0); pwd -P) 3 | . "${script_dir}/.venv/bin/activate" 4 | "${script_dir}/nand_dump_decoder.py" "$@" 5 | -------------------------------------------------------------------------------- /nand_dump_encoder.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | NAND Dump Encoder 6 | 7 | Simple software tool for encoding raw dumps for NAND memory chips using 8 | implemented error correcting codes (ECC) like BCH 9 | by Matthias Deeg 10 | 11 | uses BCH library for Python (python-bchlib) by Jeff Kent 12 | https://github.com/jkent/python-bchlib 13 | 14 | MIT License 15 | 16 | Copyright (c) 2018-2020 SySS GmbH 17 | 18 | Permission is hereby granted, free of charge, to any person obtaining a copy 19 | of this software and associated documentation files (the "Software"), to deal 20 | in the Software without restriction, including without limitation the rights 21 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 22 | copies of the Software, and to permit persons to whom the Software is 23 | furnished to do so, subject to the following conditions: 24 | 25 | The above copyright notice and this permission notice shall be included in all 26 | copies or substantial portions of the Software. 27 | 28 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 29 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 30 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 31 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 32 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 33 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 34 | SOFTWARE. 35 | """ 36 | 37 | __version__ = '0.2' 38 | __author__ = 'Matthias Deeg' 39 | 40 | import argparse 41 | import configparser 42 | import bchlib 43 | import os 44 | import struct 45 | import sys 46 | 47 | from binascii import unhexlify 48 | 49 | # BCH polynom 50 | ECC_POLY1 = 0x201b # 8219 51 | ECC_POLY2 = 0x4443 # 17475 52 | 53 | # binary dump file extension 54 | DUMP_FILE_EXTENSION = ".bin" 55 | 56 | 57 | def read_bits(value, low, high, bits=8): 58 | format_str = "0{:d}b".format(bits) 59 | 60 | n = format(value, format_str)[::-1] 61 | return int(n[low:high][::-1], 2) 62 | 63 | 64 | def reverse_bits(data): 65 | reversed_value = b'' 66 | for i in range(0, len(data)): 67 | reversed_value += bytes([int('{:08b}'.format(data[i])[::-1], 2)]) 68 | return reversed_value 69 | 70 | 71 | def xor_crypto(data, key): 72 | return bytes(a ^ b for a, b in zip(data, key)) 73 | 74 | 75 | def read_atmel_config(page, config): 76 | """Read ATMEL PMECC configuration from first page""" 77 | 78 | # read ATMEL PMECC header (52 times at the beginning of the first page) 79 | header = struct.unpack("=0.7 2 | -------------------------------------------------------------------------------- /setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | script_dir=$(cd $(dirname $0); pwd -P) 3 | cd ${script_dir} 4 | virtualenv -p python3 .venv 5 | . ./.venv/bin/activate 6 | pip install -r requirements.txt 7 | -------------------------------------------------------------------------------- /yaffs_ecc.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | """ 5 | YAFFS ECC 6 | 7 | Error correction for Yet Another Flash File System (YAFFS) 8 | created by Charles Manning 9 | 10 | Ported to Python by Matthias Deeg 11 | 12 | MIT License 13 | 14 | Copyright (C) 2002-2011 Aleph One Ltd. 15 | Copyright (c) 2020 SySS GmbH 16 | 17 | Permission is hereby granted, free of charge, to any person obtaining a copy 18 | of this software and associated documentation files (the "Software"), to deal 19 | in the Software without restriction, including without limitation the rights 20 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 21 | copies of the Software, and to permit persons to whom the Software is 22 | furnished to do so, subject to the following conditions: 23 | 24 | The above copyright notice and this permission notice shall be included in all 25 | copies or substantial portions of the Software. 26 | 27 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 28 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 29 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 30 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 31 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 32 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 33 | SOFTWARE. 34 | """ 35 | 36 | __version__ = '0.1' 37 | __author__ = 'Matthias Deeg' 38 | 39 | # bit count lookup table 40 | YAFFS_COUNT_BITS_TABLE = [ 41 | 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4, 42 | 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 43 | 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 44 | 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 45 | 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 46 | 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 47 | 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 48 | 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 49 | 1, 2, 2, 3, 2, 3, 3, 4, 2, 3, 3, 4, 3, 4, 4, 5, 50 | 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 51 | 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 52 | 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 53 | 2, 3, 3, 4, 3, 4, 4, 5, 3, 4, 4, 5, 4, 5, 5, 6, 54 | 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 55 | 3, 4, 4, 5, 4, 5, 5, 6, 4, 5, 5, 6, 5, 6, 6, 7, 56 | 4, 5, 5, 6, 5, 6, 6, 7, 5, 6, 6, 7, 6, 7, 7, 8 57 | ] 58 | 59 | # column parity lookup table for Hamming ECC 60 | COLUMN_PARITY_TABLE = [ 61 | 0x00, 0x55, 0x59, 0x0c, 0x65, 0x30, 0x3c, 0x69, 62 | 0x69, 0x3c, 0x30, 0x65, 0x0c, 0x59, 0x55, 0x00, 63 | 0x95, 0xc0, 0xcc, 0x99, 0xf0, 0xa5, 0xa9, 0xfc, 64 | 0xfc, 0xa9, 0xa5, 0xf0, 0x99, 0xcc, 0xc0, 0x95, 65 | 0x99, 0xcc, 0xc0, 0x95, 0xfc, 0xa9, 0xa5, 0xf0, 66 | 0xf0, 0xa5, 0xa9, 0xfc, 0x95, 0xc0, 0xcc, 0x99, 67 | 0x0c, 0x59, 0x55, 0x00, 0x69, 0x3c, 0x30, 0x65, 68 | 0x65, 0x30, 0x3c, 0x69, 0x00, 0x55, 0x59, 0x0c, 69 | 0xa5, 0xf0, 0xfc, 0xa9, 0xc0, 0x95, 0x99, 0xcc, 70 | 0xcc, 0x99, 0x95, 0xc0, 0xa9, 0xfc, 0xf0, 0xa5, 71 | 0x30, 0x65, 0x69, 0x3c, 0x55, 0x00, 0x0c, 0x59, 72 | 0x59, 0x0c, 0x00, 0x55, 0x3c, 0x69, 0x65, 0x30, 73 | 0x3c, 0x69, 0x65, 0x30, 0x59, 0x0c, 0x00, 0x55, 74 | 0x55, 0x00, 0x0c, 0x59, 0x30, 0x65, 0x69, 0x3c, 75 | 0xa9, 0xfc, 0xf0, 0xa5, 0xcc, 0x99, 0x95, 0xc0, 76 | 0xc0, 0x95, 0x99, 0xcc, 0xa5, 0xf0, 0xfc, 0xa9, 77 | 0xa9, 0xfc, 0xf0, 0xa5, 0xcc, 0x99, 0x95, 0xc0, 78 | 0xc0, 0x95, 0x99, 0xcc, 0xa5, 0xf0, 0xfc, 0xa9, 79 | 0x3c, 0x69, 0x65, 0x30, 0x59, 0x0c, 0x00, 0x55, 80 | 0x55, 0x00, 0x0c, 0x59, 0x30, 0x65, 0x69, 0x3c, 81 | 0x30, 0x65, 0x69, 0x3c, 0x55, 0x00, 0x0c, 0x59, 82 | 0x59, 0x0c, 0x00, 0x55, 0x3c, 0x69, 0x65, 0x30, 83 | 0xa5, 0xf0, 0xfc, 0xa9, 0xc0, 0x95, 0x99, 0xcc, 84 | 0xcc, 0x99, 0x95, 0xc0, 0xa9, 0xfc, 0xf0, 0xa5, 85 | 0x0c, 0x59, 0x55, 0x00, 0x69, 0x3c, 0x30, 0x65, 86 | 0x65, 0x30, 0x3c, 0x69, 0x00, 0x55, 0x59, 0x0c, 87 | 0x99, 0xcc, 0xc0, 0x95, 0xfc, 0xa9, 0xa5, 0xf0, 88 | 0xf0, 0xa5, 0xa9, 0xfc, 0x95, 0xc0, 0xcc, 0x99, 89 | 0x95, 0xc0, 0xcc, 0x99, 0xf0, 0xa5, 0xa9, 0xfc, 90 | 0xfc, 0xa9, 0xa5, 0xf0, 0x99, 0xcc, 0xc0, 0x95, 91 | 0x00, 0x55, 0x59, 0x0c, 0x65, 0x30, 0x3c, 0x69, 92 | 0x69, 0x3c, 0x30, 0x65, 0x0c, 0x59, 0x55, 0x00, 93 | ] 94 | 95 | 96 | def calc_even_parity(data, size=8): 97 | """Calc even parity bit of given data""" 98 | 99 | parity = 0 100 | for i in range(size): 101 | parity = parity ^ ((data >> i) & 1) 102 | 103 | return parity 104 | 105 | 106 | def yaffs_hweight8(byte): 107 | return YAFFS_COUNT_BITS_TABLE[byte] 108 | 109 | 110 | def yaffs_hweight32(dword): 111 | return yaffs_hweight8(dword & 0xff) + \ 112 | yaffs_hweight8((dword >> 8) & 0xff) + \ 113 | yaffs_hweight8((dword >> 16) & 0xff) + \ 114 | yaffs_hweight8((dword >> 24) & 0xff) 115 | 116 | 117 | def yaffs_calc_ecc_256(data): 118 | """Calc YAFFS ECC for 256 byte input""" 119 | 120 | ecc = [0] * 3 121 | col_parity = 0 122 | line_parity = 0 123 | line_parity_prime = 0 124 | 125 | for i in range(256): 126 | b = COLUMN_PARITY_TABLE[data[i]] 127 | col_parity ^= b 128 | 129 | if (b & 0x01): # odd number of bits in the byte 130 | line_parity ^= i 131 | line_parity_prime ^= ~i 132 | 133 | ecc[2] = (~col_parity & 0xff) | 0x03 134 | 135 | t = 0 136 | if (line_parity & 0x08): 137 | t |= 0x80 138 | if (line_parity_prime & 0x08): 139 | t |= 0x40 140 | if (line_parity & 0x04): 141 | t |= 0x20 142 | if (line_parity_prime & 0x04): 143 | t |= 0x10 144 | if (line_parity & 0x02): 145 | t |= 0x08 146 | if (line_parity_prime & 0x02): 147 | t |= 0x04 148 | if (line_parity & 0x01): 149 | t |= 0x02 150 | if (line_parity_prime & 0x01): 151 | t |= 0x01 152 | ecc[1] = ~t & 0xff 153 | 154 | t = 0 155 | if (line_parity & 0x80): 156 | t |= 0x80 157 | if (line_parity_prime & 0x80): 158 | t |= 0x40 159 | if (line_parity & 0x40): 160 | t |= 0x20 161 | if (line_parity_prime & 0x40): 162 | t |= 0x10 163 | if (line_parity & 0x20): 164 | t |= 0x08 165 | if (line_parity_prime & 0x20): 166 | t |= 0x04 167 | if (line_parity & 0x10): 168 | t |= 0x02 169 | if (line_parity_prime & 0x10): 170 | t |= 0x01 171 | ecc[0] = ~t & 0xff 172 | 173 | return ecc 174 | 175 | 176 | def yaffs_ecc_correct(data, read_ecc, test_ecc): 177 | """Correct single bit error""" 178 | 179 | corrected = [0] * len(data) 180 | 181 | # deltas 182 | d0 = read_ecc[0] ^ test_ecc[0] 183 | d1 = read_ecc[1] ^ test_ecc[1] 184 | d2 = read_ecc[2] ^ test_ecc[2] 185 | 186 | if ((d0 | d1 | d2) == 0): 187 | # there is no error 188 | return (0, data, read_ecc) 189 | 190 | # check for single bit error 191 | if (((d0 ^ (d0 >> 1)) & 0x55) == 0x55 and 192 | ((d1 ^ (d1 >> 1)) & 0x55) == 0x55 and 193 | ((d2 ^ (d2 >> 1)) & 0x54) == 0x54): 194 | 195 | bit = 0 196 | byte = 0 197 | 198 | if (d1 & 0x80): 199 | byte |= 0x80 200 | if (d1 & 0x20): 201 | byte |= 0x40 202 | if (d1 & 0x08): 203 | byte |= 0x20 204 | if (d1 & 0x02): 205 | byte |= 0x10 206 | if (d0 & 0x80): 207 | byte |= 0x08 208 | if (d0 & 0x20): 209 | byte |= 0x04 210 | if (d0 & 0x08): 211 | byte |= 0x02 212 | if (d0 & 0x02): 213 | byte |= 0x01 214 | 215 | if (d2 & 0x80): 216 | bit |= 0x04 217 | if (d2 & 0x20): 218 | bit |= 0x02 219 | if (d2 & 0x08): 220 | bit |= 0x01 221 | 222 | corrected[byte] ^= (1 << bit) & 0xff 223 | 224 | # corrected single bit error in data 225 | return (1, corrected, read_ecc) 226 | 227 | # check for recoverable error in ECC 228 | if ((yaffs_hweight8(d0) + yaffs_hweight8(d1) + yaffs_hweight8(d2)) == 1): 229 | 230 | read_ecc[0] = test_ecc[0] 231 | read_ecc[1] = test_ecc[1] 232 | read_ecc[2] = test_ecc[2] 233 | 234 | # corrected single bit error in ECC 235 | return (1, data, read_ecc) 236 | 237 | # unrecoverable error 238 | return (-1, data, read_ecc) 239 | 240 | 241 | def yaffs_extract_ecc(data, ecc_size, ecc_count, ignore_byte=0xff): 242 | """Extract ECC from spare area using a heuristic""" 243 | 244 | pos = 0 245 | result = [] 246 | # offset of ECC in spare area is variable, more info required 247 | for pos in range(8, len(data)): 248 | if data[pos] != ignore_byte: 249 | break 250 | 251 | if pos + (ecc_size * ecc_count) > len(data): 252 | return result 253 | 254 | for i in range(ecc_count): 255 | ecc = [0] * 3 256 | ecc[0] = data[pos + (i * ecc_size)] 257 | ecc[1] = data[pos + (i * ecc_size) + 1] 258 | ecc[2] = data[pos + (i * ecc_size) + 2] 259 | result.append(ecc) 260 | 261 | return result 262 | 263 | --------------------------------------------------------------------------------