Advertisement
wai0004

NTFS MFT parsing in Bash

Nov 7th, 2024
94
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Bash 5.98 KB | Software | 0 0
  1. #!/bin/bash
  2.  
  3. # Global variable to control verbose output
  4. VERBOSE=0
  5.  
  6. # Function to print verbose messages
  7. verbose_log() {
  8.     if [ "$VERBOSE" -eq 1 ]; then
  9.         echo "$1"
  10.     fi
  11. }
  12.  
  13. # Function to convert hexadecimal string to decimal
  14. hex_to_dec() {
  15.     echo "$((16#$1))"
  16. }
  17.  
  18. # Function to seek to a specific position in the file
  19. doseek() {
  20.     local f=$1
  21.     local n=$2
  22.     BLOCKSIZE=512
  23.     na=$((n / BLOCKSIZE))
  24.     nb=$((n % BLOCKSIZE))
  25.     dd if="$f" of=/dev/null bs=$BLOCKSIZE skip=$na 2>/dev/null
  26.     if [ $nb -ne 0 ]; then
  27.         dd if="$f" of=/dev/null bs=1 skip=$nb count=1 2>/dev/null
  28.     fi
  29. }
  30.  
  31. # Function to read a specific number of bytes from a specific position
  32. readat() {
  33.     local f=$1
  34.     local n=$2
  35.     local s=$3
  36.     doseek "$f" "$n"
  37.     dd if="$f" bs=1 count=$s 2>/dev/null
  38. }
  39.  
  40. # Function to parse the filename attribute
  41. parseFilename() {
  42.     local s=$1
  43.     ref=$(echo -n "$s" | dd bs=1 count=8 status=none | hexdump -v -e '/1 "%02x"' | rev)
  44.     ref=$(hex_to_dec "$ref")
  45.     flen=$(echo -n "$s" | dd bs=1 skip=64 count=1 status=none | hexdump -v -e '/1 "%02x"' | rev)
  46.     flen=$(hex_to_dec "$flen")
  47.     fn=$(echo -n "$s" | dd bs=1 skip=66 count=$((flen * 2)) status=none | iconv -f UTF-16LE -t UTF-8)
  48.     verbose_log "Parsed filename: $fn with reference $ref"
  49.     echo "$ref $fn"
  50. }
  51.  
  52. # Function to parse the MFT file
  53. parse_mft() {
  54.     local f=$1
  55.     local bpc=$2
  56.     local mft=$3
  57.     local out=()
  58.     for i in $(seq 0 $(($(echo -n "$mft" | wc -c) / 1024 - 1))); do
  59.         if [ $((i % 791)) -eq 0 ]; then
  60.             echo -ne "\rParsing MFT: $i/$(($(echo -n "$mft" | wc -c) / 1024))" >&2
  61.         fi
  62.         chunk=$(echo -n "$mft" | dd bs=1 skip=$((i * 1024)) count=1024 status=none)
  63.         if [ "$(echo -n "$chunk" | dd bs=1 count=4 status=none)" = "FILE" ]; then
  64.             out+=("$(parse_file "$f" $((i * 1024)) $bpc "$chunk")")
  65.         else
  66.             out+=(0)
  67.         fi
  68.     done
  69.     echo -e "\rParsing MFT: Done!              " >&2
  70.     echo "${out[@]}"
  71. }
  72.  
  73. # Function to parse a file record
  74. parse_file() {
  75.     local f=$1
  76.     local chunkoff=$2
  77.     local bpc=$3
  78.     local chunk=$4
  79.     local magic=$(echo -n "$chunk" | dd bs=1 count=4 status=none | hexdump -v -e '/1 "%02x"' | rev)
  80.     magic=$(hex_to_dec "$magic")
  81.     local usa_ofs=$(echo -n "$chunk" | dd bs=1 skip=4 count=2 status=none | hexdump -v -e '/1 "%02x"' | rev)
  82.     usa_ofs=$(hex_to_dec "$usa_ofs")
  83.     local usa_count=$(echo -n "$chunk" | dd bs=1 skip=6 count=2 status=none | hexdump -v -e '/1 "%02x"' | rev)
  84.     usa_count=$(hex_to_dec "$usa_count")
  85.     local attr_offset=$(echo -n "$chunk" | dd bs=1 skip=20 count=2 status=none | hexdump -v -e '/1 "%02x"' | rev)
  86.     attr_offset=$(hex_to_dec "$attr_offset")
  87.     local attrs=()
  88.     local pos=$attr_offset
  89.     while true; do
  90.         if [ $pos -gt $((1024 - 12)) ]; then
  91.             break
  92.         fi
  93.         type=$(echo -n "$chunk" | dd bs=1 skip=$pos count=4 status=none | hexdump -v -e '/1 "%02x"' | rev)
  94.         type=$(hex_to_dec "$type")
  95.         if [ $type -eq -1 ]; then
  96.             break
  97.         fi
  98.         size=$(echo -n "$chunk" | dd bs=1 skip=$((pos + 4)) count=4 status=none | hexdump -v -e '/1 "%02x"' | rev)
  99.         size=$(hex_to_dec "$size")
  100.         nonres=$(echo -n "$chunk" | dd bs=1 skip=$((pos + 8)) count=1 status=none | hexdump -v -e '/1 "%02x"' | rev)
  101.         nonres=$(hex_to_dec "$nonres")
  102.         namelen=$(echo -n "$chunk" | dd bs=1 skip=$((pos + 9)) count=1 status=none | hexdump -v -e '/1 "%02x"' | rev)
  103.         namelen=$(hex_to_dec "$namelen")
  104.         nameoff=$(echo -n "$chunk" | dd bs=1 skip=$((pos + 10)) count=2 status=none | hexdump -v -e '/1 "%02x"' | rev)
  105.         nameoff=$(hex_to_dec "$nameoff")
  106.         if [ $namelen -ne 0 ]; then
  107.             name=$(echo -n "$chunk" | dd bs=1 skip=$nameoff count=$((namelen * 2)) status=none | iconv -f UTF-16LE -t UTF-8)
  108.         else
  109.             name=""
  110.         fi
  111.         verbose_log "Attribute type: $type, size: $size, non-resident: $nonres, name: $name"
  112.         if [ $type -eq 48 ]; then
  113.             attrs+=("$(parseFilename "$(echo -n "$chunk" | dd bs=1 skip=$pos count=$size status=none)")")
  114.        fi
  115.        pos=$((pos + size))
  116.    done
  117.    echo "${attrs[@]}"
  118. }
  119.  
  120. # Main function
  121. main() {
  122.    disk=""
  123.    while [[ $# -gt 0 ]]; do
  124.        case $1 in
  125.            -v|--verbose)
  126.                VERBOSE=1
  127.                shift
  128.                ;;
  129.            *)
  130.                disk=$1
  131.                shift
  132.                ;;
  133.        esac
  134.    done
  135.  
  136.    if [ -z "$disk" ]; then
  137.        echo "Usage: $0 [-v|--verbose] <disk>"
  138.        exit 1
  139.    fi
  140.  
  141.    verbose_log "Running in verbose mode"
  142.  
  143.    if ! echo -n "$(readat "$disk" 3 8)" | grep -q "NTFS"; then
  144.        echo "Not an NTFS disk???"
  145.        exit 1
  146.    fi
  147.  
  148.    bps=$(echo -n "$(readat "$disk" 0xb 2)" | hexdump -v -e '/1 "%02x"' | rev)
  149.     bps=$(hex_to_dec "$bps")
  150.     spc=$(echo -n "$(readat "$disk" 0xd 1)" | hexdump -v -e '/1 "%02x"' | rev)
  151.     spc=$(hex_to_dec "$spc")
  152.     bpc=$((bps * spc))
  153.  
  154.     mft_clust=$(echo -n "$(readat "$disk" 0x30 8)" | hexdump -v -e '/1 "%02x"' | rev)
  155.     mft_clust=$(hex_to_dec "$mft_clust")
  156.     mftmirr_clust=$(echo -n "$(readat "$disk" 0x38 8)" | hexdump -v -e '/1 "%02x"' | rev)
  157.     mftmirr_clust=$(hex_to_dec "$mftmirr_clust")
  158.     clust_per_mft=$(echo -n "$(readat "$disk" 0x40 1)" | hexdump -v -e '/1 "%02x"' | rev)
  159.     clust_per_mft=$(hex_to_dec "$clust_per_mft")
  160.  
  161.     verbose_log "Bytes per sector: $bps"
  162.     verbose_log "Sectors per cluster: $spc"
  163.     verbose_log "Bytes per cluster: $bpc"
  164.     verbose_log "MFT cluster: $mft_clust"
  165.     verbose_log "MFT mirror cluster: $mftmirr_clust"
  166.     verbose_log "Clusters per MFT: $clust_per_mft"
  167.  
  168.     echo "Reading MFT" >&2
  169.     mftbytes=$(readat "$disk" $((mft_clust * bpc)) $((clust_per_mft * bpc)))
  170.     mft=$(parse_mft "$disk" $bpc "$mftbytes")
  171.     for file in $mft; do
  172.         echo "$file"
  173.     done
  174. }
  175.  
  176. if [ "$#" -lt 1 ]; then
  177.     echo "Usage: $0 [-v|--verbose] <disk>"
  178.     exit 1
  179. fi
  180.  
  181. main "$@"
  182.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement