┌───────────────────────┐
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
│                       │
└───────────────────────┘
Fuzz two legacy CVEs in libexif
~ CuB3y0nd
# Target CVEs

## Description

CVE: https://www.cve.org/CVERecord?id=CVE-2009-3895

CVE: https://www.cve.org/CVERecord?id=CVE-2012-2836

# Compile

## Download

```shellsession
git clone https://github.com/libexif/libexif.git
cd libexif && git checkout libexif-0_6_14-release
```

## Build

 make  patch 


```diff title="Makefile.am"
- SUBDIRS = m4m po libexif test doc binary
+ SUBDIRS = m4m po libexif test binary
```

```shellsession
autoreconf -fvi
CC=clang CXX=clang++ CFLAGS="-O0 -g -fno-inline -fno-builtin -fno-omit-frame-poi
nter" CXXFLAGS="$CFLAGS" ./configure --enable-shared=no --prefix="$PWD/../worksh
op/lib-debug/"
make -j`nproc` && make install
make clean

CC=afl-clang-lto CXX=afl-clang-lto++ ./configure --enable-shared=no --prefix="$P
WD/../workshop/lib-fuzz/"
make -j`nproc` && make install
```

 libexif  fuzz  harness 
便使 [exif](https://github.com/libexif/exif) 

```shellsession
git clone https://github.com/libexif/exif.git
cd exif && git checkout exif-0_6_15-release
autoreconf -fvi
CC=clang CXX=clang++ CFLAGS="-O0 -g -fno-inline -fno-builtin -fno-omit-frame-poi
nter" CXXFLAGS="$CFLAGS" PKG_CONFIG_PATH="$PWD/../workshop/lib-debug/lib/pkgconf
ig/" ./configure --enable-shared=no --prefix="$PWD/../workshop/exif-debug"
make -j`nproc` && make install
make clean

CC=afl-clang-lto CXX=afl-clang-lto++ PKG_CONFIG_PATH="$PWD/../workshop/lib-fuzz/
lib/pkgconfig/" ./configure --enable-shared=no --prefix="$PWD/../workshop/exif-f
uzz"
make -j`nproc` && make install
```

## Samples

```python title="gen_corpus.py"
#!/usr/bin/env python3

import struct
import os

def pack_data(fmt, endian, *args):
    return struct.pack(('<' if endian == 'LE' else '>') + fmt, *args)

class ExifGenerator:
    def __init__(self, endian='LE'):
        self.endian = endian
        self.entries = []
        self.data_blobs = b''

    def add_entry(self, tag, data_type, count, value):
        self.entries.append({'tag': tag, 'type': data_type, 'count': count, 'val
ue': value})

    def build_ifd(self, start_offset, next_ifd_offset=0):
        ifd_data = pack_data('H', self.endian, len(self.entries))
        data_offset = start_offset + 2 + (len(self.entries) * 12) + 4

        entry_bytes = b''
        blob_bytes = b''

        for e in self.entries:
            val_bytes = b''
            raw_blob = None

            if e['type'] == 1: # BYTE
                if isinstance(e['value'], int): raw_blob = pack_data('B', self.e
ndian, e['value'])
                else: raw_blob = e['value']
            elif e['type'] == 2: # ASCII
                raw_blob = e['value'].encode('ascii') + b'x00'
            elif e['type'] == 3: # SHORT
                if e['count'] == 1: raw_blob = pack_data('H', self.endian, e['va
lue'])
                else: raw_blob = pack_data('H'*e['count'], self.endian, *e['valu
e'])
            elif e['type'] == 4: # LONG
                if e['count'] == 1: raw_blob = pack_data('I', self.endian, e['va
lue'])
                else: raw_blob = pack_data('I'*e['count'], self.endian, *e['valu
e'])
            elif e['type'] == 5: # RATIONAL
                raw_blob = pack_data('II', self.endian, e['value'][0], e['value'
][1])
            elif e['type'] == 7: # UNDEFINED
                raw_blob = e['value']
            elif e['type'] == 10: # SRATIONAL
                raw_blob = pack_data('ii', self.endian, e['value'][0], e['value'
][1])

            if len(raw_blob) <= 4:
                val_bytes = raw_blob.ljust(4, b'x00')
            else:
                off = data_offset + len(blob_bytes)
                val_bytes = pack_data('I', self.endian, off)
                blob_bytes += raw_blob

            entry_bytes += pack_data('HHII', self.endian, e['tag'], e['type'], e
['count'], struct.unpack('<I' if self.endian == 'LE' else '>I', val_bytes)[0])

        return ifd_data + entry_bytes + pack_data('I', self.endian, next_ifd_off
set) + blob_bytes

def create_complex_exif(endian='LE'):
    # 1. Build Exif SubIFD
    exif = ExifGenerator(endian)
    exif.add_entry(0x9003, 2, 20, "2026:02:24 14:00:00")
    exif.add_entry(0x829a, 5, 1, (1, 100))
    exif.add_entry(0x829d, 5, 1, (28, 10))
    exif.add_entry(0x9204, 10, 1, (-1, 3))
    exif.add_entry(0x9286, 7, 8, b"USERCOMx00")
    exif_sub_data = exif.build_ifd(0) # Length check only first

    # 2. Build GPS IFD
    gps = ExifGenerator(endian)
    gps.add_entry(0x0000, 1, 4, b'x02x02x00x00')
    gps.add_entry(0x0002, 5, 3, (1, 1)) # This will actually need 3 rationals, b
ut let's keep it simple
    # Fix: GPS Latitude is 3 rationals
    gps.entries[-1] = {'tag': 0x0002, 'type': 5, 'count': 3, 'value': (40, 1, 30
, 1, 15, 1)}
    gps_data = gps.build_ifd(0)

    # 3. Build IFD0
    ifd0 = ExifGenerator(endian)
    ifd0.add_entry(0x010e, 2, 11, "Fuzz Test")
    ifd0.add_entry(0x0110, 2, 11, "Gemini Cam")
    ifd0.add_entry(0x8769, 4, 1, 0) # ExifOffset
    ifd0.add_entry(0x8825, 4, 1, 0) # GPSInfo

    # IFD0 fixed size: 2 + 4*12 + 4 = 54. Blobs: 11 + 11 = 22. Total = 76.
    ifd0_total_len = 76
    exif_offset = 8 + ifd0_total_len
    gps_offset = exif_offset + len(exif_sub_data)

    for e in ifd0.entries:
        if e['tag'] == 0x8769: e['value'] = exif_offset
        if e['tag'] == 0x8825: e['value'] = gps_offset

    final_ifd0 = ifd0.build_ifd(8)
    final_exif = exif.build_ifd(exif_offset)
    final_gps = gps.build_ifd(gps_offset)

    header = b'IIx2ax00x08x00x00x00' if endian == 'LE' else b'MMx00x2ax00x00x00x
08'
    return header + final_ifd0 + final_exif + final_gps

def save_corpus(name, data, add_exif_header=False):
    os.makedirs('corpus/exif', exist_ok=True)
    with open(os.path.join('corpus/exif', name), 'wb') as f:
        if add_exif_header: f.write(b'Exifx00x00')
        f.write(data)
    print(f"Created {name}")

if __name__ == "__main__":
    save_corpus('rich_le.exif', create_complex_exif('LE'), True)
    save_corpus('rich_be.exif', create_complex_exif('BE'), True)

    SOI, APP1 = b'xffxd8', b'xffxe1'
    exif_payload = b'Exifx00x00' + create_complex_exif('LE')
    app1_data = APP1 + struct.pack('>H', len(exif_payload) + 2) + exif_payload
    save_corpus('rich_jpeg.jpg', SOI + app1_data + b'xffxd9')
    save_corpus('raw_tiff.exif', create_complex_exif('LE'), False)
```

# Fuzzing

AFLplusplus go work: `afl-fuzz -i - -o out/ -s 1337 -- ./exif-fuzz/bin/exif @@`.

 Gemini Cam 
13  crashes4  hangs crash 

```plaintext showLineNumbers=false
Program received signal SIGSEGV, Segmentation fault.
0x00007ffff7e595c1 in memcpy () from /usr/lib64/libc.so.6
#0  0x00007ffff7e595c1 in memcpy () from /usr/lib64/libc.so.6
#1  0x0000555555565553 in exif_data_load_data_thumbnail (data=0x5555555931e0, d=
0x555555595986 "II*", ds=256, offset=4294967168, size=232) at exif-data.c:292
#2  0x0000555555563d05 in exif_data_load_data_content (data=0x5555555931e0, ifd=
EXIF_IFD_EXIF, d=0x555555595986 "II*", ds=256, offset=86, recursion_depth=1) at 
exif-data.c:381
#3  0x0000555555563b36 in exif_data_load_data_content (data=0x5555555931e0, ifd=
EXIF_IFD_0, d=0x555555595986 "II*", ds=256, offset=10, recursion_depth=0) at exi
f-data.c:361
#4  0x0000555555563621 in exif_data_load_data (data=0x5555555931e0, d_orig=0x555
555595980 "Exif", ds_orig=262) at exif-data.c:813
#5  0x000055555556cb9b in exif_loader_get_data (loader=0x555555593190) at exif-l
oader.c:387
#6  0x000055555555f73e in main (argc=2, argv=0x7fffffffdca8) at main.c:438

Program received signal SIGSEGV, Segmentation fault.
0x000055555556e60f in exif_get_slong (b=0x5555555b2000 <error: Cannot access mem
ory at address 0x5555555b2000>, order=EXIF_BYTE_ORDER_INTEL) at exif-utils.c:129
129     return ((b[3] << 24) | (b[2] << 16) | (b[1] << 8) | b[0]);
#0  0x000055555556e60f in exif_get_slong (b=0x5555555b2000 <error: Cannot access
 memory at address 0x5555555b2000>, order=EXIF_BYTE_ORDER_INTEL) at exif-utils.c
:129
#1  0x000055555556e49f in exif_get_long (buf=0x5555555b2000 <error: Cannot acces
s memory at address 0x5555555b2000>, order=EXIF_BYTE_ORDER_INTEL) at exif-utils.
c:156
#2  0x0000555555566026 in exif_entry_fix (e=0x555555593460) at exif-entry.c:189
#3  0x0000555555562d6d in fix_func (e=0x555555593460, data=0x0) at exif-content.
c:211
#4  0x0000555555562a13 in exif_content_foreach_entry (content=0x555555593270, fu
nc=0x555555562d50 <fix_func>, data=0x0) at exif-content.c:185
#5  0x0000555555562bdc in exif_content_fix (c=0x555555593270) at exif-content.c:
225
#6  0x0000555555565481 in fix_func (c=0x555555593270, data=0x0) at exif-data.c:1
082
#7  0x00005555555650ca in exif_data_foreach_content (data=0x5555555931e0, func=0
x5555555653e0 <fix_func>, user_data=0x0) at exif-data.c:965
#8  0x00005555555643d4 in exif_data_fix (d=0x5555555931e0) at exif-data.c:1087
#9  0x0000555555563886 in exif_data_load_data (data=0x5555555931e0, d_orig=0x555
555595980 "", ds_orig=82) at exif-data.c:826
#10 0x000055555556cbeb in exif_loader_get_data (loader=0x555555593190) at exif-l
oader.c:368
#11 0x000055555555f75e in main (argc=2, argv=0x7fffffffdbe8) at main.c:438

Program received signal SIGSEGV, Segmentation fault.
0x000055555556e3d2 in exif_get_sshort (buf=0x555655595985 <error: Cannot access 
memory at address 0x555655595985>, order=EXIF_BYTE_ORDER_INTEL) at exif-utils.c:
92
92     return ((buf[1] << 8) | buf[0]);
#0  0x000055555556e3d2 in exif_get_sshort (buf=0x555655595985 <error: Cannot acc
ess memory at address 0x555655595985>, order=EXIF_BYTE_ORDER_INTEL) at exif-util
s.c:92
#1  0x000055555556e33f in exif_get_short (buf=0x555655595985 <error: Cannot acce
ss memory at address 0x555655595985>, order=EXIF_BYTE_ORDER_INTEL) at exif-utils
.c:100
#2  0x0000555555563671 in exif_data_load_data (data=0x5555555931e0, d_orig=0x555
555595980 "Exif", ds_orig=61) at exif-data.c:775
#3  0x000055555556cbeb in exif_loader_get_data (loader=0x555555593190) at exif-l
oader.c:368
#4  0x000055555555f75e in main (argc=2, argv=0x7fffffffdbe8) at main.c:438
```

hang  `exif_loader_write` 


```bash
#!/usr/bin/env bash

TARGET="./exif-debug/bin/exif"
CRASH_DIR="./out/default/crashes"
OUTPUT_LOG="crash_reports.txt"

>"$OUTPUT_LOG"

echo "[+] Analysing crashes in $CRASH_DIR..."

for crash_file in "$CRASH_DIR"/id:*; do
  [ -e "$crash_file" ] || continue

  echo "--------------------------------------------------" >>"$OUTPUT_LOG"
  echo "File: $(basename "$crash_file")" >>"$OUTPUT_LOG"

  gdb --batch 
    --ex "run" 
    --ex "bt" 
    --ex "quit" 
    --args "$TARGET" "$crash_file" >>"$OUTPUT_LOG" 2>&1

  echo -e "n" >>"$OUTPUT_LOG"
done

echo "[+] Done! Result in: $OUTPUT_LOG"
```

# Analysis

 API [libexif Project Documentation](https
://libexif.github.io/docs.html).

~~ MC~~

## CVE-2012-2836



```plaintext showLineNumbers=false
#0  0x00007ffff7e595c1 in memcpy () from /usr/lib64/libc.so.6
#1  0x0000555555565553 in exif_data_load_data_thumbnail (data=0x5555555931e0, d=
0x555555595986 "II*", ds=256, offset=4294967168, size=232) at exif-data.c:292
#2  0x0000555555563d05 in exif_data_load_data_content (data=0x5555555931e0, ifd=
EXIF_IFD_EXIF, d=0x555555595986 "II*", ds=256, offset=86, recursion_depth=1) at 
exif-data.c:381
#3  0x0000555555563b36 in exif_data_load_data_content (data=0x5555555931e0, ifd=
EXIF_IFD_0, d=0x555555595986 "II*", ds=256, offset=10, recursion_depth=0) at exi
f-data.c:361
#4  0x0000555555563621 in exif_data_load_data (data=0x5555555931e0, d_orig=0x555
555595980 "Exif", ds_orig=262) at exif-data.c:813
#5  0x000055555556cb9b in exif_loader_get_data (loader=0x555555593190) at exif-l
oader.c:387
#6  0x000055555555f73e in main (argc=2, argv=0x7fffffffdca8) at main.c:438
```

`exif_loader_get_data`  `ExifData` 使 `exif_data_loa
d_data`  JPEG / EXIF _header_, _byte order_, _fixed value_ 
 ExifData  _IFD 0_  _IFD 1_ IF
D  `exif_data_load_data_content` 
 switch load data content  exif 


 `EXIF_TAG_EXIF_IFD_POINTER`  case  `exif_data_load_da
ta_content` ifd  `EXIF_IFD_EXIF` `exif
_data_load_data_thumbnail` 

 `c`  
`i` 13  `exif_data_load_data_thumbnail`
 `if i == 13` 
 `exif_data_load_data_thumbnail` 


 381  374 

使 `memcpy`  `rsi` 
 `exif_data_load_data_thumbnail` 

```c
// -> 0x555555563d00 <exif_data_load_data_content+1168>    call   exif_data_load
_data_thumbnail <exif_data_load_data_thumbnail>
//        rdi: 0x555555593240 -> 0x5555555932d0 -> 0x5555555934a0 -> 0x555555593
420 <- 0x200000110
//        rsi: 0x555555595986 <- 0x8002a4949 /* 'II*' */
//        rdx: 0x100
//        rcx: 0xffffff80
//        r8: 0xe8

static void
exif_data_load_data_thumbnail (ExifData *data, const unsigned char *d,
          unsigned int ds, ExifLong offset, ExifLong size)
{
 if (ds < offset + size) {
  exif_log (data->priv->log, EXIF_LOG_CODE_DEBUG, "ExifData",
     "Bogus thumbnail offset and size: %i < %i + %i.",
     (int) ds, (int) offset, (int) size);
  return;
 }
 if (data->data)
  exif_mem_free (data->priv->mem, data->data);
 data->size = size;
 data->data = exif_data_alloc (data, data->size);
 if (!data->data)
  return;
 memcpy (data->data, d + offset, data->size);
}
```

 if branch


```c
data->size = size;
data->data = exif_data_alloc (data, data->size);
memcpy (data->data, d + offset, data->size);
```

 _size_  `r8` _data_  `exif_data_alloc`  _sr
c_  `d + offset`  `rsi + 0xffffff80`
 `offset`  rsi  of
fset 

 `exif_data_load_data_thumbnail` offset 

 `0xCAFEBABE`使 `save br
eakpoints bps`  `bps`  `source bps` 
 `memcpy` 
 CVE ID `CVE-2012-2836` 


CVE: https://www.cve.org/CVERecord?id=CVE-2012-2836

## CVE-2009-3895

```plaintext showLineNumbers=false
Program received signal SIGSEGV, Segmentation fault.
0x000055555556e60f in exif_get_slong (b=0x5555555b2000 <error: Cannot access mem
ory at address 0x5555555b2000>, order=EXIF_BYTE_ORDER_INTEL) at exif-utils.c:129
129     return ((b[3] << 24) | (b[2] << 16) | (b[1] << 8) | b[0]);
#0  0x000055555556e60f in exif_get_slong (b=0x5555555b2000 <error: Cannot access
 memory at address 0x5555555b2000>, order=EXIF_BYTE_ORDER_INTEL) at exif-utils.c
:129
#1  0x000055555556e49f in exif_get_long (buf=0x5555555b2000 <error: Cannot acces
s memory at address 0x5555555b2000>, order=EXIF_BYTE_ORDER_INTEL) at exif-utils.
c:156
#2  0x0000555555566026 in exif_entry_fix (e=0x555555593460) at exif-entry.c:189
#3  0x0000555555562d6d in fix_func (e=0x555555593460, data=0x0) at exif-content.
c:211
#4  0x0000555555562a13 in exif_content_foreach_entry (content=0x555555593270, fu
nc=0x555555562d50 <fix_func>, data=0x0) at exif-content.c:185
#5  0x0000555555562bdc in exif_content_fix (c=0x555555593270) at exif-content.c:
225
#6  0x0000555555565481 in fix_func (c=0x555555593270, data=0x0) at exif-data.c:1
082
#7  0x00005555555650ca in exif_data_foreach_content (data=0x5555555931e0, func=0
x5555555653e0 <fix_func>, user_data=0x0) at exif-data.c:965
#8  0x00005555555643d4 in exif_data_fix (d=0x5555555931e0) at exif-data.c:1087
#9  0x0000555555563886 in exif_data_load_data (data=0x5555555931e0, d_orig=0x555
555595980 "", ds_orig=82) at exif-data.c:826
#10 0x000055555556cbeb in exif_loader_get_data (loader=0x555555593190) at exif-l
oader.c:368
#11 0x000055555555f75e in main (argc=2, argv=0x7fffffffdbe8) at main.c:438
```



`exif_data_fix`  exif 使
 `exif_data_foreach_content`  `exif_data_foreach
_content`  _IFD_  `fix_func` ……

 main 

 rax  `exif_entry_fix`  `exif_get_
long`

 `exif_get_long`  `exif_entry_fix`
 for  `31440`
 `e->components` 
亿 _component_ 

_components_ 西 `ExifEntry`

 exif  [^1]  `ExifEntry`  `Format``Components`  
`Data`  _format_  `unsigned short` _component_ 
 2 bytes _components_  _format_ 
 _entry_ 
 `e->components`  `e->data`  O
OB  `e->size = e->components * exif_format_get_size(e->format)` 
 _components_ 
 _size_ 


 CVE CVE-2009-3895  chall  CVE 
 crashes  hang 

CVE: https://www.cve.org/CVERecord?id=CVE-2009-3895

# Fix

## CVE-2012-2836

 offset  `exif_get_long` 


```c
  case EXIF_TAG_JPEG_INTERCHANGE_FORMAT:
   o = exif_get_long (d + offset + 12 * i + 8,
        data->priv->order);
```

 `d + offset + 12 * i + 8` 
 `uint32_t` 使 `exif_get_slong`  `int32_t` 


```c
ExifLong
exif_get_long (const unsigned char *buf, ExifByteOrder order)
{
        return (exif_get_slong (buf, order) & 0xffffffff);
}

ExifSLong
exif_get_slong (const unsigned char *b, ExifByteOrder order)
{
 if (!b) return 0;
        switch (order) {
        case EXIF_BYTE_ORDER_MOTOROLA:
                return ((b[0] << 24) | (b[1] << 16) | (b[2] << 8) | b[3]);
        case EXIF_BYTE_ORDER_INTEL:
                return ((b[3] << 24) | (b[2] << 16) | (b[1] << 8) | b[0]);
        }

 /* Won't be reached */
 return (0);
}
```

 `& 0xffffffff`  `0xffffffff` 
 4 GB

 `offset`  `ds` `ds`  _data size
_,  ds 

```diff lang="c" title="exif-data.c"
    case EXIF_TAG_JPEG_INTERCHANGE_FORMAT:
      o = exif_get_long(d + offset + 12 * i + 8, data->priv->order);
+      if (o >= ds) {
+        exif_log(data->priv->log, EXIF_LOG_CODE_CORRUPT_DATA, "ExifData",
+                 "Illegal offset value detected!");
+        abort();
      }
```

## CVE-2009-3895

 _components_  _size_ 

```diff lang="c" title="exif-entry.c"
  case EXIF_TAG_SHARPNESS:
    switch (e->format) {
    case EXIF_FORMAT_LONG:
      if (!e->parent || !e->parent->parent)
        break;
+      if (e->components <= 0 || e->components > 65535) {
+        exif_entry_log(e, EXIF_LOG_CODE_CORRUPT_DATA,
+                       "Component size is too large!");
+        abort();
+      }
+      if (e->size < e->components * exif_format_get_size(EXIF_FORMAT_LONG)) {
+        exif_entry_log(e, EXIF_LOG_CODE_CORRUPT_DATA,
+                       "Corrupted data size detected!");
+        abort();
+      };
      o = exif_data_get_byte_order(e->parent->parent);
      for (i = 0; i < e->components; i++)
        exif_set_short(
            e->data + i * exif_format_get_size(EXIF_FORMAT_SHORT), o,
            (ExifShort)exif_get_long(
                e->data + i * exif_format_get_size(EXIF_FORMAT_LONG), o));
      e->format = EXIF_FORMAT_SHORT;
      e->size = e->components * exif_format_get_size(e->format);
      e->data = exif_entry_realloc(e, e->data, e->size);
      exif_entry_log(e, EXIF_LOG_CODE_DEBUG,
                     _("Tag '%s' was of format '%s' (which is "
                       "against specification) and has been "
                       "changed to format '%s'."),
                     exif_tag_get_name(e->tag),
                     exif_format_get_name(EXIF_FORMAT_LONG),
                     exif_format_get_name(EXIF_FORMAT_SHORT));
      break;
```


 rational 使 patch 
```diff lang="c"
  case EXIF_TAG_FOCAL_LENGTH:
    switch (e->format) {
    case EXIF_FORMAT_SRATIONAL:
      if (!e->parent || !e->parent->parent)
        break;
+      if (e->components <= 0 || e->components > 65535) {
+        exif_entry_log(e, EXIF_LOG_CODE_CORRUPT_DATA,
+                       "Component size is too large!");
+        abort();
+      }
+      if (e->size < e->components * exif_format_get_size(EXIF_FORMAT_LONG)) {
+        exif_entry_log(e, EXIF_LOG_CODE_CORRUPT_DATA,
+                       "Corrupted data size detected!");
+        abort();
+      };
      o = exif_data_get_byte_order(e->parent->parent);
      for (i = 0; i < e->components; i++) {
        sr = exif_get_srational(
            e->data + i * exif_format_get_size(EXIF_FORMAT_SRATIONAL), o);
        r.numerator = (ExifLong)sr.numerator;
        r.denominator = (ExifLong)sr.denominator;
        exif_set_rational(
            e->data + i * exif_format_get_size(EXIF_FORMAT_RATIONAL), o, r);
      }
      e->format = EXIF_FORMAT_RATIONAL;
      exif_entry_log(e, EXIF_LOG_CODE_DEBUG,
                     _("Tag '%s' was of format '%s' (which is "
                       "against specification) and has been "
                       "changed to format '%s'."),
                     exif_tag_get_name(e->tag),
                     exif_format_get_name(EXIF_FORMAT_SRATIONAL),
                     exif_format_get_name(EXIF_FORMAT_RATIONAL));
      break;
```

 CVE ……

# References

[^1]: [Description of Exif file format](https://www.media.mit.edu/pia/Research/d
eepview/exif.html)