Visualize Assembly using DGML
Starting from Visual Studio 2010 Ultimate there is a cool feature called DGML (Directed Graph Markup Language).
I wrote a small script to convert the disassembled code from WinDBG into a DGML.
In order to use it, simply type the following commands under a debug session:
.shell -o LoadLibraryA.dgml -ci "uf kernel32!LoadLibraryA" cscript.exe /nologo dasm2dgml.js
A DGML file will be generated with the given name, and here is what it looks like:
Here is the source code:
var EBB = [];
var hypertext=function(s){
var r=[],L=s.length;
for(var i=0;i<L;i++){
var c=s.charAt(i);
switch(c){
case '"':r.push('"');break;
case '&':r.push('&');break;
case '<':r.push('<');break;
case '>':r.push('>');break;
default:r.push(c);}}
return r.join('');
};
var map=function(f,v){var L=v.length,r=[];for(var i=0;i<L;i++)r.push(f(v[i]));return r;};
(function(){
var blk;
var CExtendedBasicBlock = function(name, previous, next){
this.Address = '';
this.Code = [];
this.Name = name;
this.Previous = previous;
this.Next = next;
};
while(true)
{
if(WScript.StdIn.AtEndOfStream)
break;
var strSourceLine = WScript.StdIn.ReadLine().replace(/(^\s+)|(\s+$)/g, '');
if(!strSourceLine)
continue;
if(strSourceLine.match(/.*:$/))
{
blk = new CExtendedBasicBlock(strSourceLine.slice(0, -1));
EBB.push(blk);
}
else
{
blk.Address = blk.Address || strSourceLine.match(/^[^\s]+/)[0];
blk.Code.push(strSourceLine.replace(/[^\s]*\s+/, '').replace(/[^\s]*\s+/, ''));
}
}
})();
EBB = EBB.sort(function(x, y){ return x.Address == y.Address ? 0 : x.Address > y.Address ? 1 : -1; });
for(var i = 1; i < EBB.length; i++)
{
EBB[i].Previous = EBB[i - 1];
EBB[i].Previous.Next = EBB[i];
}
WScript.Echo('<DirectedGraph Background="#FFFFFF" GraphDirection="TopToBottom" xmlns="https://schemas.microsoft.com/vs/2009/dgml">');
WScript.Echo(' <Nodes>');
map(function(blk){
var content = hypertext(blk.Name + ' (' + blk.Address + ')') + '
';
map(function(instruction){
content += '
' + hypertext(instruction);
}, blk.Code);
WScript.Echo(' <Node Id="' + hypertext(blk.Name) + '" Label="' + content + '" />');
}, EBB);
WScript.Echo(' </Nodes>');
WScript.Echo(' <Links>');
map(function(blk){
map(function(instruction){
map(function(x){
var idx = instruction.indexOf(x.Name);
idx = idx >= 0 ? instruction.charAt(idx + x.Name.length) : -1;
if(idx == '' || idx == ' ')
WScript.Echo(' <Link Source="' + hypertext(blk.Name) + '" Target="' + hypertext(x.Name) + '" />');
}, EBB);
}, blk.Code);
if(blk.Next && !(blk.Code[blk.Code.length - 1].match(/^[^\s]+/)[0] in {jmp: 0, ret: 0}))
WScript.Echo(' <Link Category="FallThrough" Source="' + hypertext(blk.Name) + '" Target="' + hypertext(blk.Next.Name) + '" />');
}, EBB);
WScript.Echo(' </Links>');
WScript.Echo(' <Styles>');
WScript.Echo(' <Style TargetType="Node">');
WScript.Echo(' <Setter Property="FontFamily" Value="Consolas" />');
WScript.Echo(' <Setter Property="FontSize" Value="11" />');
WScript.Echo(' <Setter Property="Background" Value="White" />');
WScript.Echo(' <Setter Property="NodeRadius" Value="2" />');
WScript.Echo(' </Style>');
WScript.Echo(' <Style TargetType="Link">');
WScript.Echo(' <Condition Expression="HasCategory(\'FallThrough\')" />');
WScript.Echo(' <Setter Property="Background" Value="Red" />');
WScript.Echo(' <Setter Property="Stroke" Value="Red" />');
WScript.Echo(' </Style>');
WScript.Echo(' </Styles>');
WScript.Echo('</DirectedGraph>');
Notes:
- This script cannot generate 100% accurate control flow diagram, you will have to do further analysis (e.g. jmp eax).
- I haven't got a chance to test under WOA (ARM32), so I leave it as a homework for our readers.
Enjoy:)