Skip to content

Instantly share code, notes, and snippets.



Created Jun 11, 2018
What would you like to do?
import sys
import extract_icon
import os
import argparse
class MorIcons:
parses an ``APPS.INF`` file, dumps all icons from a PE binary and
generates an HTML file with all the icons and associated program
# dumps all icons and index.html in the current directory
m = MorIcons("moricons.dll", "apps.inf")
:param dll: path to ``moricons.dll``
:param inf: path to ``APPS.INF``
:param ext: extension for the images, such as ``png``
# top of the HMTL file
_HTML_HEADER = """<!doctype html>
<meta charset="utf-8"/>
width: 100px;
display: inline-block;
margin: 10px;
text-align: center;
vertical-align: top;
# bottom of the HTML file
def __init__(self, dll, inf, ext="png"):
self.dll = dll
self.inf = inf
self.ext = ext
self.icon_count = 0
self.names = self._get_names()
def _get_names(self):
opens the INF file, parses the CSV lines and returns all the
program names and icon indexes
:returns: a ``dict`` of program names indexed by the index of
the icon in the binary
# reading apps.inf
with open(self.inf, "r") as f:
lines = f.readlines()
# program names indexed by icon index in the binary
names = {}
# will be set when seeing a line that contains "[pif]", which is
# the line just before the first CSV line
started = False
for ln in lines:
ln = ln.strip()
# skip empty lines
if ln == "":
if started:
# once "[pif]" has been seen, apps.inf only has:
# 1) sections
# 2) comments
# 3) CSV lines
# 4) empty lines (handled above)
# once the section "[std_dflt]", there are no more CSV
# lines in the file
if ln == "[std_dflt]":
# ignore comments and sections
if ln[0] == ";" or ln[0] == "[":
pair = self.parse_line(ln)
if pair is not None:
names[pair["index"]] = pair["name"]
elif ln == "[pif]":
# CSV lines start here
started = True
return names
def parse_line(self, ln):
parses a single CSV line and the index and program name
:param ln: the string to parse
:returns: ``{'index': icon index, 'name': program name}``
# here is an example of a CSV line from APPS.INF:
# QD3.EXE = QD3,"Q-DOS 3",,cwe,moricons.dll,47,std_QD3,enha_QD3
# normally, the line would be split on '=' to get the
# value, then the CSV properly parsed to handle the
# double quotes around the name, but a quick check
# showed that no name actually contains a comma
# so in this example, a simple comma split makes that:
# [0] QD3.EXE = QD3
# [1] "Q-DOS 3"
# [2]
# [3] cwe
# [4] moricons.dll
# [5] 47
# [6] std_QD3
# [7] enha_QD3
# which is perfect
cols = ln.split(",")
# sanity check
if len(cols) != 8:
return None
# apps.inf also describes icons from other binaries
# (such as progman.exe), and [4] contains the filename
if cols[4] != "moricons.dll":
return None
# index of the icon in the binary
index = int(cols[5])
# name, may be surrounded by double quotes
name = cols[1].strip().strip("\"")
return {"index": index, "name": name}
def write_icons(self, dir="."):
dumps all the icons in their best format to the given directory
using their index (0-based) as the filename, in the image format
given in the constructor
:param dir: the output directory
ex = extract_icon.ExtractIcon(self.dll)
# in extract_icon, each icon is called a "group", because it
# can contain multiple icons of various size and depth
groups = ex.get_group_icons()
# remember the number of unique icons so it can be used when
# writing the HTML
self.icon_count = len(groups)
# 'i' is needed to generate the filename
for i in range(self.icon_count):
group = groups[i]
# index of the "best" icon in the group; extract_icon finds
# the largest bit count and returns the index of the widest
# icon with that bit count
# moricons.dll is mostly 1-bit (monochrome) and 4-bit, but
# the last few Office icons are 8-bit
best = ex.best_icon(group)
# creates a pillow Image out of the best icon
image = ex.export(group, best)
filename = self.icon_filename(i)
path = os.path.join(dir, filename)
# example path would be "dir/0.png"
with open(path, "wb") as f:
def write_html(self, path="index.html"):
writes an HTML file that contains a series of ``<div>`` for
each icon and program name
:param path: the path of the output file
# writes a header, an html_entry() for each icon, then a footer
with open(path, "w") as f:
for i in range(self.icon_count):
icon = self.icon_filename(i)
name = self.names.get(i, "(none)")
f.write("\n" + self.html_entry(icon, name))
def icon_filename(self, i):
makes the filename for an icon
:param i: 0-based index of the icon
:returns: a string that embed the index and extension
return str(i) + "." + self.ext
def html_entry(self, icon, name):
makes a single ``<div>`` for an icon
:param icon: path to the icon
:param name: name of the program associated with the icon
:returns: a string with an ``<img>`` and name
return '<div><img src="{}"><br>{}</div>'.format(icon, name)
if __name__ == "__main__":
p = argparse.ArgumentParser(
help="image format to use",
help="output directory",
help="name of the HTML index file",
help="path to MORICONS.DLL",
help="path to APPS.INF")
args = p.parse_args()
m = MorIcons(args.dll, args.INF, args.format)
m.write_html(os.path.join(args.output, args.index))

This comment has been minimized.

Copy link

@voxel98 voxel98 commented Nov 24, 2018


Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.