这是一种什么样的压缩/混淆算法?

逆向工程 去混淆 解压 二进制格式 异或
2021-06-22 18:08:31

我有几个流在DICOM文件中看起来很奇怪简而言之,DICOM非常接近于二进制 XML 的样子。几乎所有来自这些 DICOM 文件的信息都是直接的,可以使用DCMTK和/或GDCM很好地读取和解释

但是,文件末尾存储了两个二进制字段,看起来像是私有编码信息。由于 DICOM 主要用于系统之间的互操作性,因此供应商实际上将自己的内部文件格式存储在 DICOM 文件的字段之一中(声明为私有字段,就像 TIFF 世界中的人们会做的那样)。在我过去的经验中,编码是微不足道的(简单地struct存储为二进制),例如参见此处此处

现在,如果我从 DICOM 文件 (debian/jessie amd64) 中提取二进制 blob,我会看到以下内容:

$ gdcmraw -t 7101,1000 input.dcm file1000.gz
$ gdcmraw -t 7101,1002 input.dcm file1002.gz
$ file file1000.gz file1000.gz: gzip compressed data, max compression, from FAT filesystem (MS-DOS, OS/2, NT)

$ gunzip file1000.gz

gzip: file1000.gz: invalid compressed data--format violated

但是gunzip不能解压它们。有更多 gzip 知识的人可以检查这些文件是否真的是 gzip 压缩的吗?看起来可以解压缩它们,因为在医疗行业,我们倾向于尽可能重用代码。例如,一个众所周知的MRI厂商也采用gzip压缩的流来存储自己的专用文件格式,看到这里例如(全螺纹在这里)。

混淆也应该非常简单,因为它需要通过医疗行业的许可。根据过去的经验,我只看到使用字节反转或简单的增量异或。

我在这里上传了文件:

图像可以很好地提取,所以我怀疑只有一些额外的私人供应商信息(仅限元数据)存储在此字段(MRI 序列号...)中。

更具体地说,假gzip流成对出现,例如(file1000.gz&file1002.gz取自同一个 DICOM 文件)。从另一个 DICOM 文件中,我发现第二个假 gzip 流(按位)与 相同file1002.gz,所以我只上传file1000_other1.gz(同样适用于file1000_other2.gzfile1000_other3.gzfile1000_other4.gz)。所以也许file1002.gz这里有点特别。由于我无法物理访问生成这些图像的 MRI 工作站,因此我只能在这里使用蛮力方法。


更新:我确实检查过这些文件不仅仅是使用unz.pyrunmebinwalk -X也没有透露任何内容)的带有损坏标头的压缩码流所以它们不是直接的简单gzip文件。

更新 2:我确实尝试使用此代码向后读取流,但这仍然看起来不像放气流。

更新 3:到目前为止,我发现的所有流都有适当的 gzip 标头,并且它们都以 4 个零 (0) 字节结束,就像任何有效的 gzip 一样。我应该能够使用最后 4 个字节恢复文件,因为它们用于存储 crc32(根据 gzip RFC)。

更新 4:感谢这里的帮助,我发现这些私人标签实际上有一些记录

Table A.2.1.2.1.3-3 Private Elements for MR Scanner or MR Workstation Images
When exporting Marconi MR Scanner or MR Workstation images the following
private elements may be included.

Tag Name Value Representation
7101,0010 Private MR Creator Data element LO
7101,1000 MR Processing Field 1 OB
7101,1001 MR Processing Field 1 Length SL
7101,1002 MR Processing Field 2 OB
7101,1003 MR Processing Field 2 Length SL
7101,1004 Scan Duration SH
7101,1005 MR Processing Field 3 SH
7101,1006 MR Processing Field 4 SH

我确实检查了提取的 fake-gzip 的长度是否与存储在关联属性中的值相匹配(因此属性 7101,1000 的长度匹配存储在属性 7101,1001 中的值,而属性 7101,1002 的长度匹配存储在属性中的值7101,1003)。例如:

$ gdcmdump input3.dcm
[...]
(07a1,0010) ?? (LO) [ELSCINT1]                                    # 8,1 Private Creator
(07a1,1013) ?? (UL) 62940                                         # 4,1 ?
(7101,0000) ?? (UL) 24242                                         # 4,1 Generic Group Length
(7101,0010) ?? (LO) [Picker MR Private Group ]                    # 24,1 Private Creator
(7101,1000) ?? (OB) 1f\8b\08\00\00\00\00\00\02\00\14\5d\4b\8d\db\48\6e\3e\ec\53\4f\7b\28\c3\1e\ef\8c\d8\2d\86\e8\86\57\01\c9\d9\96\4a\bd\76\45\35\92\99\dc\33\e5\1b\08\78\04\25\94\93\04\f3\80\7a\03\fa\cd\34\02\40         # 10784,1 ?
(7101,1001) ?? (SL) 10784                                         # 4,1 ?
(7101,1002) ?? (OB) 1f\8b\08\00\00\00\00\00\02\00\14\3c\6d\8d\da\48\6d\9f\93\a1\31\e5\9c\2f\f6\6b\c1\48\44\d8\9e\26\67\ab\78\8d\1d\8a\6d\a0\80\6c\36\31\dd\95\b6\96\84\2f\13\90\a8\49\d8\0f\fe\fa\15\97\19\97\24\c0         # 13328,1 ?
(7101,1003) ?? (SL) 13328                                         # 4,1 ?
(7101,1004) ?? (SH) [00:48 ]                                      # 6,1 ?
(7101,1005) ?? (SH) [ECHO\CARDIAC]                                # 12,2 ?
(7101,1006) ?? (SH) [115204\4187\0\0 ]                            # 16,4 ?

更新 5:DICOM 只能将偶数字节长度存储为属性。一个伪造的 gzipped 流实际上被填充到下一个偶数长度,但 7101,1001 中报告的实际长度是奇数 (10765)。我已更新file1000_other4.gz为具有适当的长度(尾随字节不再是03 00 00 00,但是0B 03 00 00

1个回答

gzip 标头是有效的,但几乎立即违反了 deflate 压缩数据格式,所有文件都在不到 10 个字节的范围内。

对于提供的所有示例文件,第一个 deflate 块是一个动态块,它具有超额订阅的代码长度代码。这意味着解码该块的代码长度所需的霍夫曼代码本身是无效的。这会立即停止减压,因为无法取得进一步的进展。

最后四个字节可能不是您认为的那样。有效 gzip 文件的最后四个字节是未压缩数据的长度,模 2 32,按小端顺序排列。这些长度似乎与文件大小无关。例如file1010.gz的最后四个字节是0b 03 00 00. 它的长度是 10766。所以 10766 字节解压到 779 字节?我不这么认为。因此,倒数第二个四个字节可能也不是您对 gzip 流所期望的,即可能不是 CRC。

标头后面的数据在所有情况下似乎都是随机的(非常平坦的直方图超过 0..255),并且本身大部分是不可压缩的,这与它是压缩数据是一致的。

我尝试从 gzip 标头之后开始到结束的所有位偏移量解压缩,但没有任何乐趣。这排除了某种标题后跟有效的放气数据。