HerrewebPy/herrewebpy/firmware_forensics/memory_drawer.py

274 lines
8.6 KiB
Python
Raw Permalink Normal View History

2024-09-11 07:18:07 +00:00
# Using plotly
import plotly.graph_objects as go
import random, argparse
import numpy as np
import pandas as pd
2024-09-14 14:40:44 +00:00
"""
This script reads a CSV file with the following columns: start,end,name,order,comment,X0,LR
- name: Name of the function or location (Required)
- start: Start address of the function or location (Required)
- end: End address of the function or location (Required)
Then it generates a memory map of the regions, and outputs an HTML file with the memory map.
"""
def read_data(input_file):
data = pd.read_csv(input_file)
2024-09-11 07:18:07 +00:00
def convert_to_int(value):
try:
if isinstance(value, str) and value.startswith('0x'):
return int(value, 16)
else:
return int(value)
except ValueError:
return value
data['start'] = data['start'].apply(convert_to_int)
data['end'] = data['end'].apply(convert_to_int)
data['size'] = data['end'] - data['start']
2024-09-14 14:40:44 +00:00
#data.sort_values(by=['size'], inplace=True, ascending=False)
data.sort_values(by=['start', 'size'], inplace=True, ascending=True)
2024-09-11 07:18:07 +00:00
# Inverse the order of the data
data.reset_index(drop=True, inplace=True)
data['overlap'] = False
data['index'] = data.index
2024-09-11 07:18:07 +00:00
for i, row in data.iterrows():
data.at[i, 'overlap'] = False
2024-09-14 14:40:44 +00:00
data.at[i, 'partial_overlap'] = False
2024-09-14 14:40:44 +00:00
# Annotate rows that fully overlap the current row
temp = data.loc[(data['start'] <= row['start']) & (data['end'] >= row['end'])]
if temp.shape[0] > 1:
data.at[i, 'overlap'] = True
2024-09-14 14:40:44 +00:00
data.at[i, 'overlapped_by'] = ','.join(temp['index'].astype(str).to_list())
# Annotate rows that partially overlap the current row (from start, but not to end)
temp = data.loc[(data['start'] <= row['start']) & (data['end'] < row['end']) & (data['end'] >= row['start'])]
if temp.shape[0] > 1:
data.at[i, 'partial_overlap'] = "Bottom"
data.at[i, 'partial_overlapped_by'] = ','.join(temp['index'].astype(str).to_list())
# Annotate rows that partially overlap the current row (from end, but not to start)
temp = data.loc[(data['start'] > row['start']) & (data['end'] >= row['end']) & (data['start'] <= row['end'])]
if temp.shape[0] > 1:
data.at[i, 'partial_overlap'] = "Top"
data.at[i, 'partial_overlapped_by'] = ','.join(temp['index'].astype(str).to_list())
# Also annotate which regions this row is overlapping
temp = data.loc[(data['start'] >= row['start']) & (data['end'] <= row['end'])]
if temp.shape[0] > 1:
data.at[i, 'overlap'] = True
data.at[i, 'overlapping'] = ','.join(temp['index'].astype(str).to_list())
2024-09-11 07:18:07 +00:00
# Send warnings if sizes are negative
if (data['size'] < 0).any():
print(f'Warning: Negative sizes detected at indices {data[data["size"] < 0].index}')
return data
2024-09-11 07:18:07 +00:00
2024-09-14 14:40:44 +00:00
def draw_diagram(data, vertical_gap_percentage=0.08, horizontal_gap=0.1):
2024-09-11 07:18:07 +00:00
tickpointers = []
labels = pd.DataFrame()
def random_color():
return f'#{random.randint(0, 0xFFFFFF):06x}'
fig = go.Figure()
2024-09-14 14:40:44 +00:00
fig.update_layout(font=dict(family="Courier New, monospace"))
fig.update_layout(
plot_bgcolor='#FFFFFF',
)
2024-09-11 07:18:07 +00:00
for i, d in data.iterrows():
fillcolor = random_color()
data.at[i, 'fillcolor'] = fillcolor
2024-09-14 14:40:44 +00:00
# Set base x values. Width of the rectangle.
x0 = 1
x1 = 6
# Set base y values. Height of the rectangle.
y0 = d['index']
y1 = d['index']+1
if d['overlap'] == True:
# Row is overlapping the current row
if pd.notna(d['overlapping']):
y0 = sorted(map(int, d['overlapping'].split(',')))[0]
y1 = sorted(map(int, d['overlapping'].split(',')))[-1] + 1
if pd.notna(d['overlapped_by']):
y0 = y0 + vertical_gap_percentage
y1 = y1 - vertical_gap_percentage
x0 = x0 + horizontal_gap
x1 = x1 - horizontal_gap
if d['partial_overlap'] == "Bottom":
if pd.notna(d['partial_overlapped_by']):
y0 = y0 + 0.25 + (0.6**len(d['partial_overlapped_by'].split(',')))
#x0 = x0 + horizontal_gap
#x1 = x1 - horizontal_gap
if d['partial_overlap'] == "Top":
if pd.notna(d['partial_overlapped_by']):
y1 = y1 - (0.6**len(d['partial_overlapped_by'].split(',')))
#x0 = x0 + horizontal_gap
#x1 = x1 - horizontal_gap
2024-09-11 07:18:07 +00:00
fig.add_shape(
type="rect",
x0=x0,
x1=x1,
y0=y0+vertical_gap_percentage,
y1=y1-vertical_gap_percentage,
2024-09-14 14:40:44 +00:00
line=dict(width=1),
2024-09-11 07:18:07 +00:00
fillcolor=fillcolor,
2024-09-14 14:40:44 +00:00
opacity=0.4,
2024-09-11 07:18:07 +00:00
layer="below",
)
2024-09-14 14:40:44 +00:00
### Add middle text
2024-09-11 07:18:07 +00:00
fig.add_trace(go.Scatter
(
x=[(x0+x1)/2],
2024-09-14 14:40:44 +00:00
y=[i+0.5],
2024-09-11 07:18:07 +00:00
text=d['name'],
mode="text",
textposition="middle center",
name=d['name'],
marker=dict(
color=fillcolor,
),
))
2024-09-14 14:40:44 +00:00
### Add top-left text with d['end']
# Overlapped to the right, to make it more readable
if pd.notna(d['overlapped_by']):
fig.add_trace(go.Scatter
(
x=[(x1-0.24+horizontal_gap)],
y=[y1-0.16],
text=hex(d['end']),
mode="text",
textposition="middle center",
marker=dict(
color=fillcolor,
),
showlegend=False,
))
# Add bottom-left text with d['end']
fig.add_trace(go.Scatter
(
x=[(x1-0.24+horizontal_gap)],
y=[y0+0.14],
text=hex(d['start']),
mode="text",
textposition="middle center",
marker=dict(
color=fillcolor,
),
showlegend=False,
))
else:
fig.add_trace(go.Scatter
(
x=[(x0+0.14+horizontal_gap)],
y=[y1-0.16],
text=hex(d['end']),
mode="text",
textposition="middle center",
marker=dict(
color=fillcolor,
),
showlegend=False,
))
### Add bottom-left text with d['end']
fig.add_trace(go.Scatter
(
x=[(x0+0.14+horizontal_gap)],
y=[y0+0.14],
text=hex(d['start']),
mode="text",
textposition="middle center",
marker=dict(
color=fillcolor,
),
showlegend=False,
))
2024-09-11 07:18:07 +00:00
fig.update_xaxes(
2024-09-14 14:40:44 +00:00
range=[0, 7],
tickvals=[0, 1, 2, 3, 4, 5, 6, 7],
2024-09-11 07:18:07 +00:00
)
start_values = data['start'].sort_values()
end_values = data['end'].sort_values()
labels = []
for i, d in data.iterrows():
if i == 0:
labels.append(f'{hex(start_values.iloc[i])}')
elif i == len(data)-1:
labels.append(f'{hex(end_values.iloc[i])}')
else:
labels.append(f'{hex(start_values.iloc[i])}<br>{hex(end_values.iloc[i-1])}')
tickpointers = [i for i in range(len(data))]
fig.update_yaxes(
# tickvals=[i for i in range(len(data)+1)],
tickvals = tickpointers,
2024-09-14 14:40:44 +00:00
#ticktext= labels, # Adds labels to the left-hand side of the graph
2024-09-11 07:18:07 +00:00
griddash="longdashdot",
gridwidth=0,
gridcolor="black",
showgrid=False,
2024-09-14 14:40:44 +00:00
showticklabels=False,
2024-09-11 07:18:07 +00:00
autorange='reversed',
)
fig.update_xaxes(
showgrid=False,
showticklabels=False,
)
fig.update_layout(
width=1200,
height=1200,
autosize=True,
margin=dict(l=200, r=20, t=20, b=20),
font=dict(
size=18,
),
legend_title_text="Function/Locations",
)
return fig
def write_output(fig, output_file):
fig.write_html(f'{output_file}.html')
2024-09-11 07:18:07 +00:00
if __name__ == '__main__':
argparser = argparse.ArgumentParser()
argparser.add_argument('--input', help='Input CSV file path', required=True, type=str)
argparser.add_argument('--output', help='Output HTML filename', required=False, type=str)
2024-09-11 07:18:07 +00:00
args = argparser.parse_args()
if not args.output:
args.output = 'memory_drawer'
data = read_data(args.input)
2024-09-11 07:18:07 +00:00
fig = draw_diagram(data)
write_output(fig, args.output)