Skip to content

Instantly share code, notes, and snippets.

@koseki
Last active June 12, 2021 12:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save koseki/9737a4490128bcb8b426c22a0b7c3885 to your computer and use it in GitHub Desktop.
Save koseki/9737a4490128bcb8b426c22a0b7c3885 to your computer and use it in GitHub Desktop.
Dump RPM headers
#! /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