Exporting Vectorized Function Graphs from IDA Pro
I recently faced the problem that I wanted to place the control flow graph of a compiled function in a PDF file while still somewhat satisfying aesthetic aspirations. I get that taking a screenshot from IDA’s graph view widget does the trick most of the time, but the job becomes tedious if the exported function is larger than the screen size (implying pixel-fiddling in your favorite image editor as a consequence). In case one wants to maintain a resolution that would allow future readers to zoom onto details of a more complex graph, one quickly ends up sewing together dozens of screenshots.
IDA offers to export the displayed function graph as Geometric Description Language (GDL) file, but all spatial information such as basic block positioning and edge routing is not part of the exported file. The result is a rather poor-looking graph, that serves the purpose, but isn’t particularly enjoyable to look at. For example, the following function
when exported to GDL and converted to PNG with graph-easy
graph-easy --from gdl --input=graph.gdl --png --output=converted_from_gdl.png
looks as below:
This—in my humble optinion—isn’t particularly a treat to the eyes.
I therefore took the time to write a small plugin for IDA Pro that retrieves the graphical parameters from a particular graph view and exports them to JSON. For example the (shortened) JSON of the function above looks as follows:
{
"font_flags" : 1,
"font_name" : "Droid Sans Mono",
"font_size" : 12,
"functions" :
[
{
"basic_blocks" :
[
{
"addr_end" : 13825,
"addr_start" : 13808,
"bottom" : 273,
"bytes" : "eJwBhgB5/0FUQbwBAAAAVYn9U0CE9nRPie+J8+hm6///SIXAdBFIizhBidzrS2YPH4QAAAAAALoFAAAASI01ZFIAADH/RTHk6Lrq//9Iiekx9jH/SInCMcDo6ez//2YPH4QAAAAAAEiJ70iNNdaMAADoEQMAAEiJx0iLNT+MAADokuv//0SJ4FtdQVzDc4I7MQ==QVRBvAEAAABVif1TQIT2dE8=",
"compressed" : false,
"disasm_lines" :
[
{
"bg_color" : 4294967295,
"text" : "AA=="
},
{
"bg_color" : 4294967295,
"text" : "AA=="
},
{
"bg_color" : 4294967295,
"text" : "AA=="
},
{
"bg_color" : 4294967295,
"text" : "AQM7IF9faW50NjQgX19mYXN0Y2FsbCBzdWJfMzVGMChfX2dpZF90IGdpZCkCAwA="
},
{
"bg_color" : 4294967295,
"text" : "ARsBKDAwMDAwMDAwMDAwMDM1RjABGgEoMDAwMDAwMDAwMDAwMzVGMHN1Yl8zNUYwAhogcHJvYyBuZWFyAhsA"
},
{
"bg_color" : 4294967295,
"text" : "OyBfX3Vud2luZCB7AA=="
},
{
"bg_color" : 4294967295,
"text" : "AQVwdXNoAgUgICAgASkBIXIxMgIhAikA"
},
{
"bg_color" : 4294967295,
"text" : "AQVtb3YCBSAgICAgASkBIXIxMmQCIQIpAQksAgkgASoBDDECDAIqAA=="
},
{
"bg_color" : 4294967295,
"text" : "AQVwdXNoAgUgICAgASkBIXJicAIhAikA"
},
{
"bg_color" : 4294967295,
"text" : "AQVtb3YCBSAgICAgASkBIWVicAIhAikBCSwCCSABKgEhZWRpAiECKgA="
},
{
"bg_color" : 4294967295,
"text" : "AQVwdXNoAgUgICAgASkBIXJieAIhAikA"
},
{
"bg_color" : 4294967295,
"text" : "AQV0ZXN0AgUgICAgASkBIXNpbAIhAikBCSwCCSABKgEhc2lsAiECKgA="
},
{
"bg_color" : 4294967295,
"text" : "AQVqegIFICAgICAgASBzaG9ydCACIAEpARoBKDAwMDAwMDAwMDAwMDM2NTBsb2NfMzY1MAIaAikA"
}
],
"id" : 0,
"left" : 130,
"right" : 598,
"top" : 0
},
{
"addr_end" : 13839,
"addr_start" : 13825,
"bottom" : 424,
"bytes" : "eJwBhgB5/0FUQbwBAAAAVYn9U0CE9nRPie+J8+hm6///SIXAdBFIizhBidzrS2YPH4QAAAAAALoFAAAASI01ZFIAADH/RTHk6Lrq//9Iiekx9jH/SInCMcDo6ez//2YPH4QAAAAAAEiJ70iNNdaMAADoEQMAAEiJx0iLNT+MAADokuv//0SJ4FtdQVzDc4I7MQ==QVRBvAEAAABVif1TQIT2dE8=ie+J8+hm6///SIXAdBE=",
"compressed" : false,
"disasm_lines" :
[
{
"bg_color" : 4294967295,
"text" : "AQVtb3YCBSAgICAgASkBIWVkaQIhAikBCSwCCSABKgEhZWJwAiECKiAgICAgICAgAQI7IGdpZAICAA=="
},
{
"bg_color" : 4294967295,
"text" : "AQVtb3YCBSAgICAgASkBIWVieAIhAikBCSwCCSABKgEhZXNpAiECKgA="
},
{
"bg_color" : 4294967295,
"text" : "AQVjYWxsAgUgICAgASkBJQEoMDAwMDAwMDAwMDAwMjE3MF9nZXRncmdpZAIlAikA"
},
{
"bg_color" : 4294967295,
"text" : "AQV0ZXN0AgUgICAgASkBIXJheAIhAikBCSwCCSABKgEhcmF4AiECKgA="
},
{
"bg_color" : 4294967295,
"text" : "AQVqegIFICAgICAgASBzaG9ydCACIAEpARoBKDAwMDAwMDAwMDAwMDM2MjBsb2NfMzYyMAIaAikA"
}
],
"id" : 1,
"left" : 157,
"right" : 482,
"top" : 303
},
{
"addr_end" : 13847,
"addr_start" : 13839,
"bottom" : 861,
"bytes" : "eJwBhgB5/0FUQbwBAAAAVYn9U0CE9nRPie+J8+hm6///SIXAdBFIizhBidzrS2YPH4QAAAAAALoFAAAASI01ZFIAADH/RTHk6Lrq//9Iiekx9jH/SInCMcDo6ez//2YPH4QAAAAAAEiJ70iNNdaMAADoEQMAAEiJx0iLNT+MAADokuv//0SJ4FtdQVzDc4I7MQ==QVRBvAEAAABVif1TQIT2dE8=ie+J8+hm6///SIXAdBE=SIs4QYnc60s=",
"compressed" : false,
"disasm_lines" :
[
{
"bg_color" : 4294967295,
"text" : "AQVtb3YCBSAgICAgASkBIXJkaQIhAikBCSwCCSABKgEJWwIJASFyYXgCIQEJXQIJAioA"
},
{
"bg_color" : 4294967295,
"text" : "AQVtb3YCBSAgICAgASkBIXIxMmQCIQIpAQksAgkgASoBIWVieAIhAioA"
},
{
"bg_color" : 4294967295,
"text" : "AQVqbXACBSAgICAgASBzaG9ydCACIAEpARoBKDAwMDAwMDAwMDAwMDM2NjJsb2NfMzY2MgIaAikA"
}
],
"id" : 2,
"left" : 0,
"right" : 248,
"top" : 778
},
{
"addr_end" : 13904,
"addr_start" : 13856,
"bottom" : 746,
"bytes" : "eJwBhgB5/0FUQbwBAAAAVYn9U0CE9nRPie+J8+hm6///SIXAdBFIizhBidzrS2YPH4QAAAAAALoFAAAASI01ZFIAADH/RTHk6Lrq//9Iiekx9jH/SInCMcDo6ez//2YPH4QAAAAAAEiJ70iNNdaMAADoEQMAAEiJx0iLNT+MAADokuv//0SJ4FtdQVzDc4I7MQ==QVRBvAEAAABVif1TQIT2dE8=ie+J8+hm6///SIXAdBE=SIs4QYnc60s=ugUAAABIjTVkUgAAMf9FMeTouur//0iJ6TH2Mf9IicIxwOjp7P//Zg8fhAAAAAAA",
"compressed" : false,
"disasm_lines" :
[
{
"bg_color" : 4294967295,
"text" : "AA=="
},
{
"bg_color" : 4294967295,
"text" : "ASgwMDAwMDAwMDAwMDAzNjIwARoBKDAwMDAwMDAwMDAwMDM2MjBsb2NfMzYyMAIaAQk6AgkgICAgICAgICAgICAgICABAjsgY2F0ZWdvcnkCAgA="
},
{
"bg_color" : 4294967295,
"text" : "AQVtb3YCBSAgICAgASkBIWVkeAIhAikBCSwCCSABKgEMNQIMAioA"
},
{
"bg_color" : 4294967295,
"text" : "AQVsZWECBSAgICAgASkBIXJzaQIhAikBCSwCCSABKgEGASgwMDAwMDAwMDAwMDA4ODkwYUNhbm5vdEZpbmROYW1lXzACBgIqIAEEOyABKDAwMDAwMDAwMDAwMDg4OTAiY2Fubm90IGZpbmQgbmFtZSBmb3IgZ3JvdXAgSUQgJWx1IgIEAA=="
},
{
"bg_color" : 4294967295,
"text" : "AQV4b3ICBSAgICAgASkBIWVkaQIhAikBCSwCCSABKgEhZWRpAiECKiAgICAgICAgAQI7IGRvbWFpbm5hbWUCAgA="
},
{
"bg_color" : 4294967295,
"text" : "AQV4b3ICBSAgICAgASkBIXIxMmQCIQIpAQksAgkgASoBIXIxMmQCIQIqAA=="
},
{
"bg_color" : 4294967295,
"text" : "AQVjYWxsAgUgICAgASkBJQEoMDAwMDAwMDAwMDAwMjBGMF9kY2dldHRleHQCJQIpAA=="
},
{
"bg_color" : 4294967295,
"text" : "AQVtb3YCBSAgICAgASkBIXJjeAIhAikBCSwCCSABKgEhcmJwAiECKgA="
},
{
"bg_color" : 4294967295,
"text" : "AQV4b3ICBSAgICAgASkBIWVzaQIhAikBCSwCCSABKgEhZXNpAiECKiAgICAgICAgAQI7IGVycm51bQICAA=="
},
{
"bg_color" : 4294967295,
"text" : "AQV4b3ICBSAgICAgASkBIWVkaQIhAikBCSwCCSABKgEhZWRpAiECKiAgICAgICAgAQI7IHN0YXR1cwICAA=="
},
{
"bg_color" : 4294967295,
"text" : "AQVtb3YCBSAgICAgASkBIXJkeAIhAikBCSwCCSABKgEhcmF4AiECKiAgICAgICAgAQI7IGZvcm1hdAICAA=="
},
{
"bg_color" : 4294967295,
"text" : "AQV4b3ICBSAgICAgASkBIWVheAIhAikBCSwCCSABKgEhZWF4AiECKgA="
},
{
"bg_color" : 4294967295,
"text" : "AQVjYWxsAgUgICAgASkBJQEoMDAwMDAwMDAwMDAwMjMzMF9lcnJvcgIlAikA"
},
{
"bg_color" : 4294967295,
"text" : "AQVub3ACBSAgICAgASkBIHdvcmQgcHRyAiAgAQlbAgkBIXJheAIhAQkrAgkBIXJheAIhAQkrAgkBHzAwMDAwMDAwaAIfAQldAgkCKQA="
}
],
"id" : 3,
"left" : 137,
"right" : 891,
"top" : 454
},
{
"addr_end" : 13922,
"addr_start" : 13904,
"bottom" : 918,
"bytes" : "eJwBhgB5/0FUQbwBAAAAVYn9U0CE9nRPie+J8+hm6///SIXAdBFIizhBidzrS2YPH4QAAAAAALoFAAAASI01ZFIAADH/RTHk6Lrq//9Iiekx9jH/SInCMcDo6ez//2YPH4QAAAAAAEiJ70iNNdaMAADoEQMAAEiJx0iLNT+MAADokuv//0SJ4FtdQVzDc4I7MQ==QVRBvAEAAABVif1TQIT2dE8=ie+J8+hm6///SIXAdBE=SIs4QYnc60s=ugUAAABIjTVkUgAAMf9FMeTouur//0iJ6TH2Mf9IicIxwOjp7P//Zg8fhAAAAAAASInvSI011owAAOgRAwAASInH",
"compressed" : false,
"disasm_lines" :
[
{
"bg_color" : 4294967295,
"text" : "AA=="
},
{
"bg_color" : 4294967295,
"text" : "ASgwMDAwMDAwMDAwMDAzNjUwARoBKDAwMDAwMDAwMDAwMDM2NTBsb2NfMzY1MAIaAQk6AgkA"
},
{
"bg_color" : 4294967295,
"text" : "AQVtb3YCBSAgICAgASkBIXJkaQIhAikBCSwCCSABKgEhcmJwAiECKgA="
},
{
"bg_color" : 4294967295,
"text" : "AQVsZWECBSAgICAgASkBIXJzaQIhAikBCSwCCSABKgEkASgwMDAwMDAwMDAwMDBDMzMwdW5rX0MzMzACJAIqAA=="
},
{
"bg_color" : 4294967295,
"text" : "AQVjYWxsAgUgICAgASkBGgEoMDAwMDAwMDAwMDAwMzk3MHN1Yl8zOTcwAhoCKQA="
},
{
"bg_color" : 4294967295,
"text" : "AQVtb3YCBSAgICAgASkBIXJkaQIhAikBCSwCCSABKgEhcmF4AiECKiAgICAgICAgAQI7IHMCAgA="
}
],
"id" : 4,
"left" : 258,
"right" : 561,
"top" : 778
},
{
"addr_end" : 13942,
"addr_start" : 13922,
"bottom" : 1202,
"bytes" : "eJwBhgB5/0FUQbwBAAAAVYn9U0CE9nRPie+J8+hm6///SIXAdBFIizhBidzrS2YPH4QAAAAAALoFAAAASI01ZFIAADH/RTHk6Lrq//9Iiekx9jH/SInCMcDo6ez//2YPH4QAAAAAAEiJ70iNNdaMAADoEQMAAEiJx0iLNT+MAADokuv//0SJ4FtdQVzDc4I7MQ==QVRBvAEAAABVif1TQIT2dE8=ie+J8+hm6///SIXAdBE=SIs4QYnc60s=ugUAAABIjTVkUgAAMf9FMeTouur//0iJ6TH2Mf9IicIxwOjp7P//Zg8fhAAAAAAASInvSI011owAAOgRAwAASInHSIs1P4wAAOiS6///RIngW11BXMM=",
"compressed" : false,
"disasm_lines" :
[
{
"bg_color" : 4294967295,
"text" : "AA=="
},
{
"bg_color" : 4294967295,
"text" : "ASgwMDAwMDAwMDAwMDAzNjYyARoBKDAwMDAwMDAwMDAwMDM2NjJsb2NfMzY2MgIaAQk6AgkgICAgICAgICAgICAgICABAjsgc3RyZWFtAgIA"
},
{
"bg_color" : 4294967295,
"text" : "AQVtb3YCBSAgICAgASkBIXJzaQIhAikBCSwCCSABKgEhY3MCIQEJOgIJAQcBKDAwMDAwMDAwMDAwMEMyQThzdGRvdXQCBwIqAA=="
},
{
"bg_color" : 4294967295,
"text" : "AQVjYWxsAgUgICAgASkBJQEoMDAwMDAwMDAwMDAwMjIwMF9mcHV0c191bmxvY2tlZAIlAikA"
},
{
"bg_color" : 4294967295,
"text" : "AQVtb3YCBSAgICAgASkBIWVheAIhAikBCSwCCSABKgEhcjEyZAIhAioA"
},
{
"bg_color" : 4294967295,
"text" : "AQVwb3ACBSAgICAgASkBIXJieAIhAikA"
},
{
"bg_color" : 4294967295,
"text" : "AQVwb3ACBSAgICAgASkBIXJicAIhAikA"
},
{
"bg_color" : 4294967295,
"text" : "AQVwb3ACBSAgICAgASkBIXIxMgIhAikA"
},
{
"bg_color" : 4294967295,
"text" : "AQVyZXRuAgUA"
},
{
"bg_color" : 4294967295,
"text" : "OyB9IC8vIHN0YXJ0cyBhdCAzNUYwAA=="
},
{
"bg_color" : 4294967295,
"text" : "ARsBKDAwMDAwMDAwMDAwMDM1RjABGgEoMDAwMDAwMDAwMDAwMzVGMHN1Yl8zNUYwAhogZW5kcAIbAA=="
},
{
"bg_color" : 4294967295,
"text" : "AA=="
}
],
"id" : 5,
"left" : 101,
"right" : 459,
"top" : 948
}
],
"bitness" : 64,
"bytes" : "eJwBhgB5/0FUQbwBAAAAVYn9U0CE9nRPie+J8+hm6///SIXAdBFIizhBidzrS2YPH4QAAAAAALoFAAAASI01ZFIAADH/RTHk6Lrq//9Iiekx9jH/SInCMcDo6ez//2YPH4QAAAAAAEiJ70iNNdaMAADoEQMAAEiJx0iLNT+MAADokuv//0SJ4FtdQVzDc4I7MQ==",
"color" : 4294967295,
"edges" :
[
{
"color" : 188,
"coords" :
[
"354 273",
"354 288",
"319 288",
"319 303"
],
"dest_id" : 1,
"source_id" : 0
},
{
"color" : 37120,
"coords" :
[
"374 273",
"374 288",
"901 288",
"901 764",
"419 764",
"419 778"
],
"dest_id" : 4,
"source_id" : 0
},
{
"color" : 188,
"coords" :
[
"309 424",
"309 439",
"122 439",
"122 760",
"124 760",
"124 778"
],
"dest_id" : 2,
"source_id" : 1
},
{
"color" : 37120,
"coords" :
[
"329 424",
"329 439",
"514 439",
"514 454"
],
"dest_id" : 3,
"source_id" : 1
},
{
"color" : 13320960,
"coords" :
[
"124 861",
"124 933",
"270 933",
"270 948"
],
"dest_id" : 5,
"source_id" : 2
},
{
"color" : 13320960,
"coords" :
[
"514 746",
"514 760",
"399 760",
"399 778"
],
"dest_id" : 4,
"source_id" : 3
},
{
"color" : 13320960,
"coords" :
[
"409 918",
"409 933",
"290 933",
"290 948"
],
"dest_id" : 5,
"source_id" : 4
}
],
"end" : 13942,
"error" : "",
"flags" : 21504,
"frame_pointer_delta" : 0,
"frame_size" : 24,
"has_graph" : true,
"name" : "sub_35F0",
"start" : 13808,
"valid" : true
}
]
}
The JSON then is consumed by a small python script that produces a scalable vector graphic (SVG):
This isn’t pixel-perfect either, but the result is good enough for my needs. To
embed the graph into a PDF, I turned the SVG into a PDF via rsvg-convert
:
rsvg-convert -f pdf -o output.pdf input.svg