Last active
June 12, 2021 12:22
-
-
Save koseki/9737a4490128bcb8b426c22a0b7c3885 to your computer and use it in GitHub Desktop.
Dump RPM headers
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#! /usr/bin/env python | |
import struct | |
from ctypes import * | |
import io | |
import re | |
from enum import Enum | |
from sys import argv | |
""" | |
Linux Standard Base Core Specification, Generic Part - 25.2. Package File Format | |
https://refspecs.linuxfoundation.org/LSB_5.0.0/LSB-Core-generic/LSB-Core-generic/pkgformat.html | |
""" | |
class IndexTypeValue(Enum): | |
NULL = 0 | |
CHAR = 1 | |
INT8 = 2 | |
INT16 = 3 | |
INT32 = 4 | |
INT64 = 5 | |
STRING = 6 | |
BIN = 7 | |
STRING_ARRAY = 8 | |
I18NSTRING = 9 | |
def ctype_size(self): | |
return { | |
self.NULL: 1, | |
self.CHAR: 1, | |
self.INT8: 1, | |
self.INT16: 2, | |
self.INT32: 4, | |
self.INT64: 8, | |
self.STRING: 1, | |
self.BIN: 1, | |
self.STRING_ARRAY: 1, | |
self.I18NSTRING: 1, | |
}[self] | |
def format_char(self): | |
format_chars = { | |
self.INT8: 'C', | |
self.INT16: 'H', | |
self.INT32: 'I', | |
self.INT64: 'L', | |
} | |
if self in format_chars: | |
return format_chars[self] | |
return None | |
class SignatureTagValue(Enum): | |
RPMTAG_HEADERSIGNATURES = 62 | |
RPMTAG_HEADERIMMUTABLE = 63 | |
RPMTAG_HEADERI18NTABLE = 100 | |
RPMSIGTAG_SIZE = 1000 | |
RPMSIGTAG_PAYLOADSIZE = 1007 | |
RPMSIGTAG_SHA1 = 269 | |
RPMSIGTAG_MD5 = 1004 | |
RPMSIGTAG_DSA = 267 | |
RPMSIGTAG_RSA = 268 | |
RPMSIGTAG_PGP = 1002 | |
RPMSIGTAG_GPG = 1005 | |
class HeaderTagValue(Enum): | |
RPMTAG_HEADERSIGNATURES = 62 | |
RPMTAG_HEADERIMMUTABLE = 63 | |
RPMTAG_HEADERI18NTABLE = 100 | |
RPMTAG_NAME = 1000 | |
RPMTAG_VERSION = 1001 | |
RPMTAG_RELEASE = 1002 | |
RPMTAG_SUMMARY = 1004 | |
RPMTAG_DESCRIPTION = 1005 | |
RPMTAG_SIZE = 1009 | |
RPMTAG_DISTRIBUTION = 1010 | |
RPMTAG_VENDOR = 1011 | |
RPMTAG_LICENSE = 1014 | |
RPMTAG_PACKAGER = 1015 | |
RPMTAG_GROUP = 1016 | |
RPMTAG_URL = 1020 | |
RPMTAG_OS = 1021 | |
RPMTAG_ARCH = 1022 | |
RPMTAG_SOURCERPM = 1044 | |
RPMTAG_ARCHIVESIZE = 1046 | |
RPMTAG_RPMVERSION = 1064 | |
RPMTAG_COOKIE = 1094 | |
RPMTAG_DISTURL = 1123 | |
RPMTAG_PAYLOADFORMAT = 1124 | |
RPMTAG_PAYLOADCOMPRESSOR = 1125 | |
RPMTAG_PAYLOADFLAGS = 1126 | |
RPMTAG_PREIN = 1023 | |
RPMTAG_POSTIN = 1024 | |
RPMTAG_PREUN = 1025 | |
RPMTAG_POSTUN = 1026 | |
RPMTAG_PREINPROG = 1085 | |
RPMTAG_POSTINPROG = 1086 | |
RPMTAG_PREUNPROG = 1087 | |
RPMTAG_POSTUNPROG = 1088 | |
RPMTAG_OLDFILENAMES = 1027 | |
RPMTAG_FILESIZES = 1028 | |
RPMTAG_FILEMODES = 1030 | |
RPMTAG_FILERDEVS = 1033 | |
RPMTAG_FILEMTIMES = 1034 | |
RPMTAG_FILEMD5S = 1035 | |
RPMTAG_FILELINKTOS = 1036 | |
RPMTAG_FILEFLAGS = 1037 | |
RPMTAG_FILEUSERNAME = 1039 | |
RPMTAG_FILEGROUPNAME = 1040 | |
RPMTAG_FILEDEVICES = 1095 | |
RPMTAG_FILEINODES = 1096 | |
RPMTAG_FILELANGS = 1097 | |
RPMTAG_DIRINDEXES = 1116 | |
RPMTAG_BASENAMES = 1117 | |
RPMTAG_DIRNAMES = 1118 | |
RPMTAG_PROVIDENAME = 1047 | |
RPMTAG_REQUIREFLAGS = 1048 | |
RPMTAG_REQUIRENAME = 1049 | |
RPMTAG_REQUIREVERSION = 1050 | |
RPMTAG_CONFLICTFLAGS = 1053 | |
RPMTAG_CONFLICTNAME = 1054 | |
RPMTAG_CONFLICTVERSION = 1055 | |
RPMTAG_OBSOLETENAME = 1090 | |
RPMTAG_PROVIDEFLAGS = 1112 | |
RPMTAG_PROVIDEVERSION = 1113 | |
RPMTAG_OBSOLETEFLAGS = 1114 | |
RPMTAG_OBSOLETEVERSION = 1115 | |
RPMTAG_BUILDTIME = 1006 | |
RPMTAG_BUILDHOST = 1007 | |
RPMTAG_FILEVERIFYFLAGS = 1045 | |
RPMTAG_CHANGELOGTIME = 1080 | |
RPMTAG_CHANGELOGNAME = 1081 | |
RPMTAG_CHANGELOGTEXT = 1082 | |
RPMTAG_OPTFLAGS = 1122 | |
RPMTAG_RHNPLATFORM = 1131 | |
RPMTAG_PLATFORM = 1132 | |
# https://github.com/rpm-software-management/rpm/blob/847c6f062c267c4be643be9c202141bd330a7891/lib/rpmtag.h | |
RPMTAG_PATCHESNAME = 1133 | |
RPMTAG_PATCHESFLAGS = 1134 | |
RPMTAG_PATCHESVERSION = 1135 | |
RPMTAG_CACHECTIME = 1136 | |
RPMTAG_CACHEPKGPATH = 1137 | |
RPMTAG_CACHEPKGSIZE = 1138 | |
RPMTAG_CACHEPKGMTIME = 1139 | |
RPMTAG_FILECOLORS = 1140 | |
RPMTAG_FILECLASS = 1141 | |
RPMTAG_CLASSDICT = 1142 | |
RPMTAG_FILEDEPENDSX = 1143 | |
RPMTAG_FILEDEPENDSN = 1144 | |
RPMTAG_DEPENDSDICT = 1145 | |
RPMTAG_SOURCEPKGID = 1146 | |
RPMTAG_FILECONTEXTS = 1147 | |
RPMTAG_FSCONTEXTS = 1148 | |
RPMTAG_RECONTEXTS = 1149 | |
RPMTAG_POLICIES = 1150 | |
RPMTAG_PRETRANS = 1151 | |
RPMTAG_POSTTRANS = 1152 | |
RPMTAG_PRETRANSPROG = 1153 | |
RPMTAG_POSTTRANSPROG = 1154 | |
RPMTAG_DISTTAG = 1155 | |
RPMTAG_OLDSUGGESTSNAME = 1156 | |
RPMTAG_FILEDIGESTALGO = 5011 | |
RPMTAG_BUGURL = 5012 | |
class RPMLead(BigEndianStructure): | |
_fields_ = ( | |
('magic', c_char * 4), | |
('major', c_char), | |
('minor', c_char), | |
('type', c_short), | |
('archnum', c_short), | |
('name', c_char * 66), | |
('osnum', c_short), | |
('signature_type', c_short), | |
('reserved', c_char * 16), | |
) | |
class RPMHeader(BigEndianStructure): | |
_fields_ = ( | |
('magic', c_char * 3), | |
('version', c_char), | |
('reserved', c_char * 4), | |
('nindex', c_int), | |
('hsize', c_int), | |
) | |
class RPMHeaderIndex(BigEndianStructure): | |
position = -1 | |
tag_name = "Unknown" | |
_fields_ = ( | |
('tag', c_int), | |
('type', c_int), | |
('offset', c_int), | |
('count', c_int), | |
) | |
def read_string(source:io.IOBase): | |
result = [] | |
while True: | |
chunk = source.read(128) | |
i = chunk.find(b'\0') | |
if i == -1: | |
result.append(chunk) | |
else: | |
result.append(chunk[:i]) | |
source.seek(i - 128 + 1, io.SEEK_CUR) | |
break | |
if len(chunk) < 128: | |
break | |
return b''.join(result) | |
def read_string_array(source:io.IOBase, length:int): | |
result = [] | |
for i in range(length): | |
result.append(read_string(source)) | |
return result | |
def main(rpmfile): | |
source = open(rpmfile, 'rb') | |
print("==== Lead Section ====") | |
lead = RPMLead() | |
source.readinto(lead) | |
for field in lead._fields_: | |
v = getattr(lead, field[0]) | |
print(f"{field[0]:>10}: {v}") | |
dump_header_structure(source, "Signature Section", SignatureTagValue) | |
dump_header_structure(source, "Header Section", HeaderTagValue) | |
source.close() | |
def dump_header_structure(source, label, tag_value): | |
print(f"==== {label} - Header ====") | |
header = RPMHeader() | |
source.readinto(header) | |
for field in header._fields_: | |
v = getattr(header, field[0]) | |
print(f"{field[0]:>10}: {v}") | |
print(f"==== {label} - Index Entries ====") | |
index_entries = [] | |
for i in range(header.nindex): | |
e = RPMHeaderIndex() | |
source.readinto(e) | |
e.position = i | |
index_entries.append(e) | |
try: | |
e.tag_name = tag_value(e.tag).name | |
except: | |
pass | |
t = IndexTypeValue(e.type).name | |
print(f"[{i:>3}] tag:{e.tag:>5} type: {t:<12} offset:{e.offset:>4} count:{e.count:>4} # {e.tag_name}") | |
print(f"==== {label} - Data ====") | |
index_entries = sorted(index_entries, key=lambda e: e.offset) | |
start = source.tell() | |
for index in index_entries: | |
index_type = IndexTypeValue(index.type) | |
size = index_type.ctype_size() | |
source.seek(start + index.offset) | |
if index_type == IndexTypeValue.STRING: | |
v = read_string(source) | |
elif index_type == IndexTypeValue.STRING_ARRAY: | |
v = read_string_array(source, index.count) | |
v = ' ' + '\n '.join(map(str, v)) | |
else: | |
v = source.read(size * index.count) | |
format_char = index_type.format_char() | |
if format_char: | |
v = ' ' + '\n '.join(map(str, struct.unpack('>' + format_char * index.count, v))) | |
else: | |
v = re.sub('([0-9a-f]{4})', '\\1 ', re.sub('(.{32})', '\\1\n', v.hex())) | |
v = v.strip() | |
print(f"[{index.position:>3}] {index.tag_name}\n{v}") | |
print("") | |
main(argv[1]) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment