-
-
Save Jasemalsadi/94e4ae01cd051f07dc64c6bc113f0c09 to your computer and use it in GitHub Desktop.
Windbg JS script to print all calls as json tree structure to easily view it, call only trace_calls function
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| //"use strict"; | |
| var addrresses_we_return_to = []; | |
| class CallTreeNode { | |
| constructor(name, address,is_outside_exedll=false) { | |
| this.name = name; | |
| this.address = address; | |
| this.is_outside_exedll = is_outside_exedll | |
| this.children = []; | |
| } | |
| addChild(node,is_outside_exedll=false) { | |
| this.children.push(node); | |
| if (!is_outside_exedll) | |
| addrresses_we_return_to.push(node.address) | |
| } | |
| // Find a node by function name | |
| findByName(name) { // make sure name is not the same | |
| if (this.name === name) return this; | |
| for (let child of this.children) { | |
| let result = child.findByName(name); | |
| if (result) return result; | |
| } | |
| return null; | |
| } | |
| findParentOfAddress(address, parent = null) { | |
| for (let child of this.children) { | |
| if (child.address === address) { | |
| return parent; // Parent node found | |
| } | |
| let parentOfChild = child.findParentOfAddress(address, child); | |
| if (parentOfChild) return parentOfChild; | |
| } | |
| return null; // No parent found with a child of the given address | |
| } | |
| // Find a node by function address | |
| findByAddress(address) { // address need to be string with 0x | |
| if (this.address === address) return this; | |
| for (let child of this.children) { | |
| let result = child.findByAddress(address); | |
| if (result) return result; | |
| } | |
| return null; | |
| } | |
| toJSON() { | |
| return { | |
| name: this.name, | |
| address: this.address, | |
| children: this.children.map(child => child.toJSON()) | |
| }; | |
| } | |
| traverse(indent = 0) { | |
| logln(' '.repeat(indent * 2) + this.name); | |
| this.children.forEach(child => child.traverse(indent + 1)); | |
| } | |
| printToFile(intent = 0) { | |
| var file = fso.CreateTextFile("C:\\Scripts\\trace_function_call_output.txt", true); | |
| file.WriteLine("This is some text output."); | |
| file.WriteLine("Add more lines as needed."); | |
| file.Close(); | |
| } | |
| } | |
| function writeTreeToFile(rootNode, filePath) { | |
| var logFilePath = filePath; | |
| var logFile; | |
| if (host.namespace.Debugger.Utility.FileSystem.FileExists(logFilePath)) { | |
| logFile = host.namespace.Debugger.Utility.FileSystem.CreateFile(logFilePath, "OpenExisting"); | |
| } | |
| else { | |
| logFile = host.namespace.Debugger.Utility.FileSystem.CreateFile(logFilePath); | |
| } | |
| var treeJson = JSON.stringify(rootNode.toJSON(), null, 2); // Pretty print with 2-space indentation | |
| //logln(treeJson); | |
| var textWriter = host.namespace.Debugger.Utility.FileSystem.CreateTextWriter(logFile, "Utf16"); | |
| try { | |
| textWriter.Write(treeJson) | |
| } | |
| finally { | |
| logFile.Close(); | |
| } | |
| } | |
| function trace_calls(start_end=undefined) { | |
| var binary_name = "" | |
| if (typeof start_end == 'undefined' || start_end === null) { | |
| var a = host.namespace.Debugger.Utility.Control.ExecuteCommand("lm a $exentry")[2] | |
| start_end = a.split(" ")[0] +","+a.split(" ")[1] | |
| binary_name = a.split(" ")[4] | |
| } | |
| /* | |
| bp wsock32!recv | |
| .scriptunload a.js | |
| .scriptload C:\Users\HA\Documents\a.js | |
| .scriptdebug C:\Users\HA\Documents\a.js | |
| sxe en | |
| */ | |
| // .scriptdebug C:\Users\HA\Documents\a.js | |
| // sxe en | |
| // q | |
| // dx @$scriptContents.trace_calls("00400000,00c0c000") | |
| // Paste the json input on : | |
| // https://jsoncrack.com/editor | |
| // Choose unfold names to get the full function name | |
| // Export it as SVG | |
| var start = parseInt("0x" + start_end.split(",")[0], 16) | |
| var end = parseInt("0x" + start_end.split(",")[1], 16) | |
| var rootNode = null | |
| var call_trace = ""; | |
| var tmp = host.namespace.Debugger.Utility.Control.ExecuteCommand("k"); | |
| var currentNode = {}; | |
| // Getting previous nodes | |
| for(var k =1;;k++) { | |
| try { | |
| var currentCall = tmp[k] | |
| var CallAddress = currentCall.split(" ")[2] | |
| var callAddressHex = parseInt("0x" + CallAddress, 16) | |
| if (callAddressHex < start || callAddressHex > end) { // we reached to root node that has our main | |
| var rootfunction = tmp[k+1] | |
| rootNode = new CallTreeNode(rootfunction, '0x' + rootfunction.split(" ")[2],false); // we make it false since we can return to it | |
| /* | |
| rootNode.addChild(new CallTreeNode("rootfunction", '0x' + rootfunction.split(" ")[2]) ) | |
| rootNode.addChild(new CallTreeNode("rootfunction2", '0x' + rootfunction.split(" ")[2]) ) | |
| rootNode.addChild(new CallTreeNode("rootfunction3", '0x' + rootfunction.split(" ")[2]) ) | |
| */ | |
| // Right now we need to add childs | |
| var prevChild = rootNode | |
| for(var k2 =k;k2>=1;k2--) { | |
| var currentChildCall = tmp[k2] | |
| var ChildCallAddress = "0x"+currentChildCall.split(" ")[2] | |
| var childNode = new CallTreeNode(currentChildCall, ChildCallAddress); | |
| prevChild.addChild(childNode) | |
| prevChild = childNode | |
| } | |
| currentNode = prevChild; | |
| break; | |
| } | |
| // currentNode += tmp[k] + "\n" | |
| }catch (err) { | |
| break; | |
| } | |
| } | |
| var i = 0; var j = 0; | |
| var isOutsideAddress = false | |
| while(1) { | |
| if (!isOutsideAddress) { // | |
| host.namespace.Debugger.Utility.Control.ExecuteCommand("t"); // tct: Target executes until it reaches a call instruction or return instruction. | |
| }else { | |
| host.namespace.Debugger.Utility.Control.ExecuteCommand("pt"); | |
| host.namespace.Debugger.Utility.Control.ExecuteCommand("t"); | |
| isOutsideAddress = false | |
| } | |
| var current_instruction = host.namespace.Debugger.Utility.Control.ExecuteCommand("u eip")[1]; | |
| var currentEIPwithut0x = host.namespace.Debugger.Utility.Control.ExecuteCommand("r eip")[0].split("=")[1]; | |
| var currentEIPRaw = "0x" + host.namespace.Debugger.Utility.Control.ExecuteCommand("r eip")[0].split("=")[1]; | |
| var currentEIP = parseInt("0x" + host.namespace.Debugger.Utility.Control.ExecuteCommand("r eip")[0].split("=")[1],16) | |
| if (current_instruction.includes("call")) { | |
| // We check next instruciton inside function being called to see if eip will be going to address outside our range | |
| host.namespace.Debugger.Utility.Control.ExecuteCommand("t"); | |
| // dx Debugger.State.Scripts.a.Contents.evalee("host.namespace.Debugger.Utility.Control.ExecuteCommand(\"dds esp L1\")") | |
| var nextInstruction = "0x"+ host.namespace.Debugger.Utility.Control.ExecuteCommand("dds esp L1")[0].split(" ")[2]; | |
| var currentNextEIP = parseInt("0x" + host.namespace.Debugger.Utility.Control.ExecuteCommand("r eip")[0].split("=")[1],16) | |
| var newCall = new CallTreeNode(current_instruction, nextInstruction); | |
| if (currentNextEIP >= start && currentNextEIP <= end) { // we have call belonging to current exe/dll address space | |
| // let's check if it's a callback from outside adddress space to our own address space | |
| if (currentEIP < start || currentNextEIP > end) { // we have a callback !! | |
| newCall.name = newCall.name + "(It's callback!)" | |
| } | |
| currentNode.addChild(newCall) | |
| currentNode = newCall | |
| i++; | |
| }else { // it's function call outside our address | |
| currentNode.addChild(newCall,true) | |
| isOutsideAddress = true // so we can skip it using pt command | |
| } // we didn't handle if code has call the call directly in assembly, which is very very unusal | |
| } else if (addrresses_we_return_to.includes(currentEIPRaw)) { | |
| currentNode = rootNode.findParentOfAddress(currentEIPRaw) | |
| if (currentNode.address=== rootNode.address) // we reached rootnode address, so we can just exit | |
| break; | |
| } | |
| /* | |
| else if (current_instruction.includes("ret")) { | |
| host.namespace.Debugger.Utility.Control.ExecuteCommand("t"); | |
| currentEIPRaw = "0x" + host.namespace.Debugger.Utility.Control.ExecuteCommand("r eip")[0].split("=")[1]; | |
| if(addrresses_we_return_to.includes(currentEIPRaw)) { // We arrived a return after a call | |
| // find the return | |
| currentNode = rootNode.findByAddress(currentEIPRaw) | |
| if (currentNode.address=== rootNode.address) // we reached rootnode address, so we can just exit | |
| break; | |
| } | |
| // we return | |
| logln("goods") | |
| } | |
| */ | |
| // host.namespace.Debugger.Utility.Control.ExecuteCommand(".cls") | |
| //if (i!=j) | |
| if ((i+1)%50 == 0) { // we print every 50 calls | |
| rootNode.traverse(0) | |
| var timestamp = new Date().getTime(); | |
| writeTreeToFile(rootNode,"C:\\Scripts\\"+binary_name+"_output_"+timestamp+".json") | |
| return; | |
| } | |
| if(i>10000) { | |
| break; | |
| } | |
| j = i | |
| } | |
| } | |
| function evalee(data){ | |
| logln(eval(data)) | |
| } | |
| function logln(e) { | |
| host.diagnostics.debugLog('\n'+e + '\n\n\n'); | |
| } | |
| function initializeScript() | |
| { | |
| // | |
| // Return an array of registration objects to modify the object model of the debugger | |
| // See the following for more details: | |
| // | |
| // https://aka.ms/JsDbgExt | |
| // | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment