public bool fillOutHeapsStack(oSingleData call) { // Check to see if this is already in the heaps list HEAP_INFO heap = oMemoryFunctions.LookupAddressInMap(oProcess.map, call.esp); if (selectedHeaps.Contains(heap.heapAddress)) { return(false); } selectedHeaps.Add(heap.heapAddress, heap); return(false); }
private static bool isValidCodeBlock(Process process, HEAP_INFO heap, uint address, List <jmpInstruction> jmps, List <retAddress> rets, uint maxLength) { // Convert this block to a linear block byte[] block = getLinearCodeToRet(process, heap, address, jmps, rets, maxLength); // Check if this is a valid code block //byte[] block = oMemoryFunctions.readMemory(process, address, addressEnd - address); int warnings = LengthDecoder.CountWarnings(block); if (warnings > 0) { return(false); } return(true); }
/// <summary> /// Returns the row and column text for a listgridview of functions. /// </summary> /// <param name="row">Cell row</param> /// <param name="column">Cell column</param> /// <returns></returns> public string getFunctionListCell(int row, int column, oVisPlayBar playBar) { // Get this row and column if (row < functions.Count && row >= 0 && functions[row] != null) { switch (column) { case 0: // Get the address description for this function HEAP_INFO heap = oMemoryFunctions.LookupAddressInMap(oProcess.map, functions[row].address); string name; // Generate the string representation of this address if (heap.associatedModule != null) { name = heap.associatedModule.ModuleName + " + 0x" + (functions[row].address - (uint)heap.associatedModule.BaseAddress).ToString("X"); } else { name = "0x" + functions[row].address.ToString("X"); } // Add the known name on a second line if (functions[row].name != "") { name = functions[row].name + " - " + name; } if (functions[row].disabled) { name = "DISABLED " + name; } return(name); case 1: // Address return(functions[row].address.ToString("X")); case 2: // Call count if (dataVis != null) { // Lets count the number of calls int tmpCount = dataVis.getCallCount(functions[row].address); // If we are near the maximum number of calls, lets add a + sign. This is because this function // stopped recorded further calls for performance reasons. (eg. a function being called millions // of times a second uses up a lot of diskspace to record!) string totalCount = tmpCount < Properties.Settings.Default.MaxRecordedCalls ? tmpCount.ToString() : tmpCount.ToString() + @"+"; // Get the selected region call count string selectedRegionCount; if (playBar != null) { selectedRegionCount = playBar.countCallSelected(functions[row].address).ToString(); } else { selectedRegionCount = ""; } // Return the two results return(String.Concat(totalCount, "/", selectedRegionCount)); } return("no data"); case 3: // Number of arguments and calling convention return(String.Concat(functions[row].getNumParams().ToString(), " args")); default: return("?"); } } return("?"); }
private static byte[] getLinearCodeToRet(Process process, HEAP_INFO heap, uint address, List <jmpInstruction> jmps_in, List <retAddress> rets, uint maxLength) { // This gets the block of data starting at address, following all force jumps, and ending at the first return. byte[] result = new byte[0]; bool cloned = false; List <jmpInstruction> jmps = jmps_in; uint curAddress = address; retAddress.COMPARER_ADDRESS retComparer = new retAddress.COMPARER_ADDRESS(); jmpInstructionComparer jmpComparer = new jmpInstructionComparer(); while (curAddress >= heap.heapAddress && curAddress < heap.heapAddress + heap.heapLength) { // Find the next return or jump - whichever comes first. // Find the next return int retIndex = rets.BinarySearch(new retAddress(curAddress, 0), retComparer); if (retIndex < 0) { retIndex = ~retIndex; } uint nextRet = uint.MaxValue; if (retIndex < rets.Count && retIndex >= 0) { nextRet = rets[retIndex].address; } // Find the next jump int jmpIndex = jmps.BinarySearch(new jmpInstruction(curAddress, 0), jmpComparer); if (jmpIndex < 0) { jmpIndex = ~jmpIndex; } uint nextJmp = uint.MaxValue; if (jmpIndex < jmps.Count && jmpIndex >= 0) { nextJmp = jmps[jmpIndex].address; } // Make sure we found either a next jump or return if (nextJmp != uint.MaxValue || nextRet != uint.MaxValue) { // Add this region to the result uint blockEnd = (nextJmp > nextRet ? nextRet : nextJmp) - 1; uint newLength = (uint)result.Length + (blockEnd - curAddress + 1); if (newLength > maxLength) { newLength = maxLength; } byte[] newResult = new byte[newLength]; Array.ConstrainedCopy(result, 0, newResult, 0, result.Length); byte[] tmp = oMemoryFunctions.ReadMemory(process, curAddress, (uint)(newResult.Length - result.Length)); Array.ConstrainedCopy(tmp, 0, newResult, result.Length, tmp.Length); result = newResult; // Check if we have gathered enough bytes if (result.Length >= maxLength) { return(result); } // If this is a return we are done, jmp we need to update the current address if (nextJmp > nextRet) { // We hit the return statement next. We are done. return(result); } else { // Update the current address to the jmp target curAddress = jmps[jmpIndex].destination; // Delete this jmp so that we do not follow it more than once if (!cloned) { jmps = new List <jmpInstruction>(jmps_in); cloned = true; } jmps.RemoveAt(jmpIndex); } } else { // No return or jump coming up, we are done. return(result); } } // We jumped out of this heap. Stop analysis. return(result); }
/// <summary> /// This disassmebles all the modules /// </summary> public static void disassembleProcess(Form parent, List <HEAP_INFO> selectedHeaps) { // Create the progress bar formProgress progress = new formProgress(parent); progress.Show(parent); progress.setMin(0); progress.setMax(selectedHeaps.Count); progress.setTitle("Disassembling Modules..."); progress.setLabel1("Generating Memory Map"); progress.setLabel2("Total Discovered Internal Calls: 0"); // Reset the function master oFunctionMaster.reset(); // Create the return instruction list, for parameter count estimation usage later on oAsmRetList returnAddresses = new oAsmRetList(); oEbpArgumentList ebpAddresses = new oEbpArgumentList(); // Process the selected heaps ulong count = 0; HeaderReader headerReader = null; for (int i = 0; i < selectedHeaps.Count; i++) { HEAP_INFO heap = selectedHeaps[i]; // Check if it is a PE Header if (heap.extra.Contains("PE")) { // Process the PE import table headerReader = new HeaderReader(activeProcess, heap.heapAddress); // Set the access rights to this heap so that we can edit the header if (headerReader.importTable.Count > 0) { HEAP_INFO tmpHeapInfo = oMemoryFunctions.LookupAddressInMap(map, headerReader.optHeader.DataDirectory13.VirtualAddress + (uint)heap.heapAddress); if (tmpHeapInfo.heapProtection.Contains("EXECUTE")) { oMemoryFunctions.SetMemoryProtection(activeProcess, (uint)tmpHeapInfo.heapAddress, (uint)tmpHeapInfo.heapLength, MEMORY_PROTECT.PAGE_EXECUTE_READWRITE); } else { oMemoryFunctions.SetMemoryProtection(activeProcess, (uint)tmpHeapInfo.heapAddress, (uint)tmpHeapInfo.heapLength, MEMORY_PROTECT.PAGE_READWRITE); } } // Add all the imported functions foreach (IMPORT_FUNCTION importFunction in headerReader.importTable) { // Lookup the destination address HEAP_INFO destinationHeap = oMemoryFunctions.LookupAddressInMap(map, (uint)importFunction.targetAddress); // Add this function if it is valid if (destinationHeap.heapProtection.Contains("EXECUTE") && destinationHeap.heapLength > 0) { // Valid destination oFunctionMaster.addCall((uint)importFunction.memoryAddress, (uint)importFunction.targetAddress, oFunction.CALL_TYPE.PE_TABLE, importFunction.name); // Disasemble only one function and find return (needed for parametar count estimation) uint length = (uint) (destinationHeap.heapLength - (importFunction.targetAddress - destinationHeap.heapAddress)); if (length > 0x1000) { length = 0x1000; } byte[] block = oMemoryFunctions.ReadMemory(activeProcess, importFunction.targetAddress, length); // Process the function only LengthDecoder.AddFirstRet(block, (int)importFunction.targetAddress, 0, ref returnAddresses); } } // Add all the exported functions if (headerReader.exports.Count > 0) { // Read in this heap HEAP_INFO tmpHeap; byte[] block = null; IEnumerator exportEnum = headerReader.exports.Values.GetEnumerator(); if (exportEnum.MoveNext()) { tmpHeap = oMemoryFunctions.LookupAddressInMap(map, (uint)((export)exportEnum.Current).Address); block = oMemoryFunctions.ReadMemory(activeProcess, tmpHeap.heapAddress, (uint)tmpHeap.heapLength); // We need to add all the exported functions from this library foreach (export exportFunction in headerReader.exports.Values) { if ((exportFunction.Address >= tmpHeap.heapAddress) && (exportFunction.Address <= tmpHeap.heapAddress + tmpHeap.heapLength)) { // Add this call oFunctionMaster.addCall(0, (uint)exportFunction.Address, oFunction.CALL_TYPE.PE_EXPORT, exportFunction.Name); // Disasemble only one function and find return (needed for parameter count estimation) // Process the function only LengthDecoder.AddFirstRet(block, (int)tmpHeap.heapAddress, (int)(exportFunction.Address - tmpHeap.heapAddress), ref returnAddresses); } } } } } // If this contains EXECUTE privileges, instrument this heap even if it is a PE header. // Instrument this heap if it has been selected, but not if it is a PE header region without EXECUTE privileges. if (!heap.extra.Contains("PE") || heap.heapProtection.Contains("EXECUTE")) { // Disassemble this heap as a code region List <jmpInstruction> jmpAddresses = new List <jmpInstruction>(10000); // Let's process this data region if (heap.associatedModule != null) { progress.setLabel1("Analyzing code region for module " + heap.associatedModule.ModuleName + " heap at address 0x" + heap.heapAddress.ToString("X")); } else { progress.setLabel1( "Analyzing code region associated with an unknown module at address 0x" + heap.heapAddress.ToString("X")); } // Read in the heap byte[] block = oMemoryFunctions.ReadMemory(activeProcess, heap.heapAddress, (uint)heap.heapLength); // Process the heap List <SIMPLE_INSTRUCTION> instructions = LengthDecoder.DisassembleBlockCallsOnly(block, (int)heap.heapAddress, ref returnAddresses, ref ebpAddresses, ref jmpAddresses); // Get a list of the return instruction addresses for this heap List <retAddress> retAddresses = returnAddresses.getRets((uint)heap.heapAddress, (uint)(heap.heapAddress + heap.heapLength)); // Generate a list of call source-destination pairs. List <SOURCE_DEST_PAIR> callPairs = new List <SOURCE_DEST_PAIR>(1000); for (int n = 0; n < instructions.Count; n++) { HEAP_INFO destinationHeap = oMemoryFunctions.LookupAddressInMap(map, (uint) instructions[n].jmp_target); if (destinationHeap.heapLength > 0 && destinationHeap.heapAddress == heap.heapAddress && isValidCodePointer(activeProcess, instructions[n].address, (uint)instructions[n].jmp_target, headerReader) && isValidCodeBlock(activeProcess, heap, instructions[n].address, jmpAddresses, retAddresses, 500) && isValidCodeBlock(activeProcess, heap, instructions[n].jmp_target, jmpAddresses, retAddresses, 500)) { // Add this call, it may be filtered out later. callPairs.Add(new SOURCE_DEST_PAIR((uint)instructions[n].address, (uint)instructions[n].jmp_target, oFunction.CALL_TYPE.FIXED_OFFSET, null, "")); } } // ----------------------------------- // PROPER CALLBACK TABLE CONDITION // ----------------------------------- // The start of the callback table is defined as: // 1. 4 dwords in a row that point to within this heap. // 2. Must point to at least 2 different addresses. // 3. All 4 pointers must point to valid code regions. // // The end of the callback table is defined as: // 1. first not heap-internal pointer // 2. or first heap-internal pointer that fails the // valid function test. // bool inCallbackTable = false; for (int n = 0; n < block.Length - 4 * 4; n += 4) { // Check if this could be the start of a callback table. uint value1 = oMemoryFunctions.ByteArrayToUint(block, n); if (inCallbackTable) { // Check is this value should be added onto the callback table if (value1 >= heap.heapAddress && value1 <= heap.heapAddress + heap.heapLength && isValidCodePointer(activeProcess, ((uint)n) + (uint)heap.heapAddress, value1, headerReader) && isValidCodeBlock(activeProcess, heap, value1, jmpAddresses, retAddresses, 500)) { // Another valid callback entry callPairs.Add(new SOURCE_DEST_PAIR(((uint)n) + (uint)heap.heapAddress, value1, oFunction.CALL_TYPE.CALLBACK_TABLE, callPairs[callPairs.Count - 1], "vtable")); } else { // End of callback table. inCallbackTable = false; } } else if (value1 >= heap.heapAddress && value1 <= heap.heapAddress + heap.heapLength) { // Check second pointer uint value2 = oMemoryFunctions.ByteArrayToUint(block, n + 4); if (value2 >= heap.heapAddress && value2 <= heap.heapAddress + heap.heapLength) { // Check third pointer uint value3 = oMemoryFunctions.ByteArrayToUint(block, n + 4 * 2); if (value3 >= heap.heapAddress && value3 <= heap.heapAddress + heap.heapLength) { // Check fourth pointer uint value4 = oMemoryFunctions.ByteArrayToUint(block, n + 4 * 3); if (value4 >= heap.heapAddress && value4 <= heap.heapAddress + heap.heapLength) { // We found four internal pointers in a row. Check that condition #2 is met. if (!((value1 == value2 && value1 == value3) || (value1 == value2 && value1 == value4) || (value2 == value3 && value2 == value4))) { // At least two of the values are different // Check condition #3, all valid code poitners if (isValidCodePointer(activeProcess, ((uint)n) + (uint)heap.heapAddress, value1, headerReader) && isValidCodeBlock(activeProcess, heap, value1, jmpAddresses, retAddresses, 500) && isValidCodePointer(activeProcess, ((uint)n + 4 * 1) + (uint)heap.heapAddress, value2, headerReader) && isValidCodeBlock(activeProcess, heap, value2, jmpAddresses, retAddresses, 500) && isValidCodePointer(activeProcess, ((uint)n + 4 * 2) + (uint)heap.heapAddress, value3, headerReader) && isValidCodeBlock(activeProcess, heap, value3, jmpAddresses, retAddresses, 500) && isValidCodePointer(activeProcess, ((uint)n + 4 * 3) + (uint)heap.heapAddress, value4, headerReader) && isValidCodeBlock(activeProcess, heap, value4, jmpAddresses, retAddresses, 500)) { // All pointers are valid, start of callback table inCallbackTable = true; // Add these function calls callPairs.Add(new SOURCE_DEST_PAIR(((uint)n) + (uint)heap.heapAddress, value1, oFunction.CALL_TYPE.CALLBACK_TABLE, null, "vtable")); callPairs.Add(new SOURCE_DEST_PAIR(((uint)n + 4 * 1) + (uint)heap.heapAddress, value2, oFunction.CALL_TYPE.CALLBACK_TABLE, callPairs[callPairs.Count - 1], "vtable")); callPairs.Add(new SOURCE_DEST_PAIR(((uint)n + 4 * 2) + (uint)heap.heapAddress, value3, oFunction.CALL_TYPE.CALLBACK_TABLE, callPairs[callPairs.Count - 1], "vtable")); callPairs.Add(new SOURCE_DEST_PAIR(((uint)n + 4 * 3) + (uint)heap.heapAddress, value4, oFunction.CALL_TYPE.CALLBACK_TABLE, callPairs[callPairs.Count - 1], "vtable")); // Move on to the next callback n += 4 * 3; // because n+=4 } } } } } } } // ----------------------------------- // PROPER SUBROUTINE ENTER FILTER CONDITION: // ----------------------------------- // Condition Description: // - each subroutine is only called from the start of the subroutine, and no call // can enter the subroutine in the middle of the function. I have yet to see a // function that violates rule even under obfuscation (except rarely the skipping // of hotpatching bytes). // // Condition Implementation: // - only smallest call destination in range (retAddress[i-1], retAddress[i]] is valid // for funcion i. // - a destination of min destionation + const, where 0 <= const <= 2 is allowed to support // some obfuscation methods that skip the hotpatching useless bytes. // // Sort the array by the destination SOURCE_DEST_PAIR.COMPARER_DEST comparerDest = new SOURCE_DEST_PAIR.COMPARER_DEST(); callPairs.Sort(comparerDest); // Loop through each return instruction for (int n = 0; n < retAddresses.Count; n++) { // Find all destination addresses associated with this function // Find the start destination pair index int indexStart = (n > 0 ? callPairs.BinarySearch(new SOURCE_DEST_PAIR(0, retAddresses[n - 1].address, oFunction.CALL_TYPE.FIXED_OFFSET, null, ""), comparerDest) : 0); if (indexStart < 0) { indexStart = ~indexStart; } // Find the end destination pair index int indexEnd = callPairs.BinarySearch(new SOURCE_DEST_PAIR(0, retAddresses[n].address, oFunction.CALL_TYPE.FIXED_OFFSET, null, ""), comparerDest); if (indexEnd < 0) { indexEnd = ~indexEnd - 1; } // If we have more than one call to this procedure, loop through them and maybe filter them out if (indexEnd - indexStart + 1 > 1) { // Find the start of the subroutine uint minDestination = uint.MaxValue; for (int m = indexStart; m <= indexEnd; m++) { if (callPairs[m].dest < minDestination) { minDestination = callPairs[m].dest; } } // Loop through these call destinations to make sure they are not entering in the middle of the subroutine for (int m = indexStart; m <= indexEnd; m++) { if (callPairs[m].dest > minDestination + 2) { // This is entering the subrouting in the middle! Remove this call as invalid. callPairs.RemoveAt(m); m--; indexEnd--; } } } } // Add the calls to the function list) for (int n = 0; n < callPairs.Count; n++) { // Add this function call if (disassemblyMode.recordIntramodular) { oFunctionMaster.addCall(callPairs[n].source, callPairs[n].dest, callPairs[n].type, callPairs[n].name); } } count = count + (ulong)instructions.Count; } progress.setLabel2("Total Discovered Internal Calls: " + count); progress.increment(); } progress.Dispose(); // Estimate number of parameters for each function oFunctionMaster.estimateFunctionParameters(returnAddresses, ebpAddresses, parent); }
private string replaceConstants(string code) { // This replaces all the # # constants with their appropriate values. // List of constants to replace iin this code: // #MODULENAME# - Module name containing the function // #FUNCTIONOFFSET# - Offset of the function // #FUNCTIONNAME# - Name of the function. eg. kernel32_4a029 // #NUMSTACKARGS# - Number of arguments passed on the stack // #MAINMODULENAME# - The name of the main module of the process // #CALLCODE# - The binary responsible for setting up the stack and registers in the other process. // #PROCESSNAME# - The name of the process // // List of constants that the C#, delphi, vb, etc is expected to replace in the built ASM string, #CALLCODE#: // #FUNCTION# - This is the address of the function. It varies according to the loading module base. // #_ECX__# - With arugment value // #_EDX__# - With arugment value // #_EAX__# - With arugment value // #ARGN01# - With arugment value // #ARGN02# - With arugment value // #ARGN03# - With arugment value // #ARGN04# - With arugment value // ... // Initialize the result string result = code; HEAP_INFO heap = oMemoryFunctions.LookupAddressInMap(oProcess.map, function.address); if (heap.heapAddress != 0) { // Replace the process name result = result.Replace("#PROCESSNAME#", oProcess.activeProcess.ProcessName); // Replace the module name result = result.Replace("#MODULENAME#", heap.associatedModule.ModuleName); // Replace the function offset if (heap.associatedModule != null) { result = result.Replace("#FUNCTIONOFFSET#", "0x" + (function.address - (uint)heap.associatedModule.BaseAddress).ToString("X")); } else { result = result.Replace("#FUNCTIONOFFSET#", "0x" + (function.address - heap.heapAddress + 0x1000).ToString("X")); } // Replace the function name string name = ""; if (heap.associatedModule != null) { name = heap.associatedModule.ModuleName + "_" + (function.address - (uint)heap.associatedModule.BaseAddress).ToString("X"); } else { name = heap.associatedModule.ModuleName + "_" + (function.address - heap.heapAddress + 0x1000).ToString("X"); } name = name.Replace(".", "_"); for (int i = 0; i < name.Length; i++) { if ((name[i] < 48 || name[i] > 57) && (name[i] < 65 || name[i] > 90) && (name[i] < 97 || name[i] > 122) && name[i] != 95) { // Invalid character for a function name, remove it name = name.Remove(i, 1); i--; } } // Cannot be 0 length and cannot start with a number if (name.Length <= 0 || (name[0] >= 48 && name[0] <= 57)) { name = "f_" + name; } result = result.Replace("#FUNCTIONNAME#", name); // Replace the number of stack arguments result = result.Replace("#NUMSTACKARGS#", function.getNumParams().ToString()); // Generate and replace the call code List <string> stackArgNames = new List <string>((int)function.getNumParams()); for (int i = 1; i <= function.getNumParams(); i++) { string argNum = i.ToString(); while (argNum.Length < 2) { argNum = "0" + argNum; } stackArgNames.Add("#ARGN" + argNum + "#"); } string asmCode = oAssemblyGenerator.processLabels(oAssemblyGenerator.replaceCommands( function.generateThreadCallCode("#_ECX__#", "#_EDX__#", "#_EAX__#", stackArgNames).Replace("\n", " ").Replace("\r", ""))); asmCode = oAssemblyGenerator.buildInjectionStringOnly(0, asmCode.Replace("<destination>", "#FUNCTION#"), oProcess.activeProcess, function.address, 0); result = result.Replace("#CALLCODE#", asmCode); } return(result); }