├── assets ├── ghidra_raw_decompile_result.png ├── ghidra_raw_decompile_result2.png ├── ghidra_refined_decompile_result.png └── ghidra_refined_decompile_result2.png ├── ghidra_scripts ├── hash_db.json ├── ModifyDecompileResultFromFunctionName.java └── ModifyDecompileResultFromApiHash.java ├── LICENSE ├── SampleCode ├── ResolveByAPIHash │ ├── ResolveByAPIHash.vcxproj.filters │ ├── ResolveByAPIHash.vcxproj │ └── Main.c ├── ResolveByAPIName │ ├── ResolveByAPIName.vcxproj.filters │ ├── Main.c │ └── ResolveByAPIName.vcxproj └── SampleCode.sln ├── README.md └── .gitignore /assets/ghidra_raw_decompile_result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kohnakagawa/PracticalPCode/HEAD/assets/ghidra_raw_decompile_result.png -------------------------------------------------------------------------------- /assets/ghidra_raw_decompile_result2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kohnakagawa/PracticalPCode/HEAD/assets/ghidra_raw_decompile_result2.png -------------------------------------------------------------------------------- /assets/ghidra_refined_decompile_result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kohnakagawa/PracticalPCode/HEAD/assets/ghidra_refined_decompile_result.png -------------------------------------------------------------------------------- /assets/ghidra_refined_decompile_result2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kohnakagawa/PracticalPCode/HEAD/assets/ghidra_refined_decompile_result2.png -------------------------------------------------------------------------------- /ghidra_scripts/hash_db.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "user32.dll", 4 | "hash": 271684975 5 | }, 6 | { 7 | "name": "MessageBoxA", 8 | "hash": 356901485 9 | }, 10 | { 11 | "name": "ntdll.dll", 12 | "hash": 3751111590 13 | }, 14 | { 15 | "name": "NtAllocateVirtualMemory", 16 | "hash": 2609387939 17 | }, 18 | { 19 | "name": "NtFlushInstructionCache", 20 | "hash": 1438816864 21 | }, 22 | { 23 | "name": "LdrLoadDll", 24 | "hash": 2133165252 25 | }, 26 | { 27 | "name": "RtlUnicodeStringToAnsiString", 28 | "hash": 3988010069 29 | }, 30 | { 31 | "name": "RtlAnsiStringToUnicodeString", 32 | "hash": 474962788 33 | }, 34 | { 35 | "name": "RtlInitAnsiString", 36 | "hash": 1105978905 37 | }, 38 | { 39 | "name": "RtlFreeUnicodeString", 40 | "hash": 22365590 41 | } 42 | ] 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2021 Koh M. Nakagawa 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /SampleCode/ResolveByAPIHash/ResolveByAPIHash.vcxproj.filters: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | ソース ファイル 20 | 21 | 22 | -------------------------------------------------------------------------------- /SampleCode/ResolveByAPIName/ResolveByAPIName.vcxproj.filters: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | ソース ファイル 20 | 21 | 22 | -------------------------------------------------------------------------------- /SampleCode/ResolveByAPIName/Main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | typedef int (*pfnMessageBoxA)( 5 | HWND hWnd, 6 | LPCSTR lpText, 7 | LPCSTR lpCaption, 8 | UINT uType 9 | ); 10 | typedef BOOL (*pfnCreateProcessA)( 11 | LPCSTR lpApplicationName, 12 | LPSTR lpCommandLine, 13 | LPSECURITY_ATTRIBUTES lpProcessAttributes, 14 | LPSECURITY_ATTRIBUTES lpThreadAttributes, 15 | BOOL bInheritHandles, 16 | DWORD dwCreationFlags, 17 | LPVOID lpEnvironment, 18 | LPCSTR lpCurrentDirectory, 19 | LPSTARTUPINFOA lpStartupInfo, 20 | LPPROCESS_INFORMATION lpProcessInformation 21 | ); 22 | typedef DWORD (*pfnGetModuleFileNameA)( 23 | HMODULE hModule, 24 | LPSTR lpFilename, 25 | DWORD nSize 26 | ); 27 | typedef HANDLE (*pfnCreateThread)( 28 | LPSECURITY_ATTRIBUTES lpThreadAttributes, 29 | SIZE_T dwStackSize, 30 | LPTHREAD_START_ROUTINE lpStartAddress, 31 | __drv_aliasesMem LPVOID lpParameter, 32 | DWORD dwCreationFlags, 33 | LPDWORD lpThreadId 34 | ); 35 | 36 | pfnMessageBoxA messageBoxA = NULL; 37 | pfnCreateProcessA createProcessA = NULL; 38 | pfnGetModuleFileNameA getModuleFileNameA = NULL; 39 | pfnCreateThread createThread = NULL; 40 | 41 | void InitializeFunctionPointers() { 42 | // NOTE: 43 | // The following Win32 API addresses are stored as global variables 44 | // Win32 API functions are called through these global variables 45 | HMODULE kernel32Base = LoadLibraryA("kernel32.dll"); 46 | if (kernel32Base) { 47 | createProcessA = (pfnCreateProcessA)GetProcAddress(kernel32Base, "CreateProcessA"); 48 | getModuleFileNameA = (pfnGetModuleFileNameA)GetProcAddress(kernel32Base, "GetModuleFileNameA"); 49 | createThread = (pfnCreateThread)GetProcAddress(kernel32Base, "CreateThread"); 50 | } 51 | HMODULE user32Base = LoadLibraryA("user32.dll"); 52 | if (user32Base) { 53 | messageBoxA = (pfnMessageBoxA)GetProcAddress(user32Base, "MessageBoxA"); 54 | } 55 | 56 | printf("%p %p %p %p\n", createProcessA, getModuleFileNameA, createThread, messageBoxA); 57 | } 58 | 59 | int main() { 60 | InitializeFunctionPointers(); 61 | messageBoxA(NULL, "Hi", "Hello", MB_OK); 62 | return 0; 63 | } -------------------------------------------------------------------------------- /SampleCode/SampleCode.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.30804.86 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ResolveByAPIName", "ResolveByAPIName\ResolveByAPIName.vcxproj", "{CC2C9E4C-C3C2-44B9-A2CC-67CC9A4297B0}" 7 | EndProject 8 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ResolveByAPIHash", "ResolveByAPIHash\ResolveByAPIHash.vcxproj", "{A3E94D84-3C04-4678-9130-ED800E88C31C}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|x64 = Debug|x64 13 | Debug|x86 = Debug|x86 14 | Release|x64 = Release|x64 15 | Release|x86 = Release|x86 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {CC2C9E4C-C3C2-44B9-A2CC-67CC9A4297B0}.Debug|x64.ActiveCfg = Debug|x64 19 | {CC2C9E4C-C3C2-44B9-A2CC-67CC9A4297B0}.Debug|x64.Build.0 = Debug|x64 20 | {CC2C9E4C-C3C2-44B9-A2CC-67CC9A4297B0}.Debug|x86.ActiveCfg = Debug|Win32 21 | {CC2C9E4C-C3C2-44B9-A2CC-67CC9A4297B0}.Debug|x86.Build.0 = Debug|Win32 22 | {CC2C9E4C-C3C2-44B9-A2CC-67CC9A4297B0}.Release|x64.ActiveCfg = Release|x64 23 | {CC2C9E4C-C3C2-44B9-A2CC-67CC9A4297B0}.Release|x64.Build.0 = Release|x64 24 | {CC2C9E4C-C3C2-44B9-A2CC-67CC9A4297B0}.Release|x86.ActiveCfg = Release|Win32 25 | {CC2C9E4C-C3C2-44B9-A2CC-67CC9A4297B0}.Release|x86.Build.0 = Release|Win32 26 | {A3E94D84-3C04-4678-9130-ED800E88C31C}.Debug|x64.ActiveCfg = Debug|x64 27 | {A3E94D84-3C04-4678-9130-ED800E88C31C}.Debug|x64.Build.0 = Debug|x64 28 | {A3E94D84-3C04-4678-9130-ED800E88C31C}.Debug|x86.ActiveCfg = Debug|Win32 29 | {A3E94D84-3C04-4678-9130-ED800E88C31C}.Debug|x86.Build.0 = Debug|Win32 30 | {A3E94D84-3C04-4678-9130-ED800E88C31C}.Release|x64.ActiveCfg = Release|x64 31 | {A3E94D84-3C04-4678-9130-ED800E88C31C}.Release|x64.Build.0 = Release|x64 32 | {A3E94D84-3C04-4678-9130-ED800E88C31C}.Release|x86.ActiveCfg = Release|Win32 33 | {A3E94D84-3C04-4678-9130-ED800E88C31C}.Release|x86.Build.0 = Release|Win32 34 | EndGlobalSection 35 | GlobalSection(SolutionProperties) = preSolution 36 | HideSolutionNode = FALSE 37 | EndGlobalSection 38 | GlobalSection(ExtensibilityGlobals) = postSolution 39 | SolutionGuid = {BF74307D-3823-44DF-8B44-ED0E68382414} 40 | EndGlobalSection 41 | EndGlobal 42 | -------------------------------------------------------------------------------- /ghidra_scripts/ModifyDecompileResultFromFunctionName.java: -------------------------------------------------------------------------------- 1 | // @author Ko Nakagawa 2 | // @category PCode 3 | // @keybinding 4 | // @menupath 5 | // @toolbar 6 | 7 | import ghidra.app.decompiler.DecompInterface; 8 | import ghidra.app.decompiler.DecompileOptions; 9 | import ghidra.app.script.GhidraScript; 10 | import ghidra.program.model.pcode.*; 11 | import ghidra.program.model.symbol.*; 12 | 13 | public class ModifyDecompileResultFromFunctionName extends GhidraScript { 14 | 15 | private boolean isTargetFunction(VarnodeAST vn, String fncName) { 16 | if (!vn.isAddress()) return false; 17 | 18 | final var addr = vn.getAddress(); 19 | final var fnc = getSymbolAt(addr); 20 | if (fnc == null) return false; 21 | 22 | return fnc.toString().contains(fncName); 23 | } 24 | 25 | private String findProcName(PcodeOpAST pcGetProcAddress) { 26 | // NOTE: get the varnode of the second argument 27 | VarnodeAST vn = (VarnodeAST) pcGetProcAddress.getInput(2); 28 | 29 | // get varnode of copy source 30 | PcodeOp copyPcode = vn.getDef(); 31 | if (copyPcode == null) { 32 | return null; 33 | } 34 | 35 | VarnodeAST vnIn = (VarnodeAST) copyPcode.getInput(0); 36 | if (!vnIn.isConstant() && !vnIn.isAddress()) { 37 | return null; 38 | } 39 | 40 | final var nameDataAtConst = vnIn.getAddress(); 41 | if (nameDataAtConst == null) { 42 | return null; 43 | } 44 | 45 | // NOTE: Cannot get string data at const address space, thus convert it to ram address space 46 | final var nameDataAtRamSpace = toAddr(nameDataAtConst.getUnsignedOffset()); 47 | final var nameString = getDataAt(nameDataAtRamSpace); 48 | if (nameString == null) { 49 | return null; 50 | } 51 | 52 | final var procName = nameString.getDefaultValueRepresentation(); 53 | if (procName == null) { 54 | return null; 55 | } 56 | 57 | // NOTE: remove "" 58 | return procName.substring(1, procName.length() - 1); 59 | } 60 | 61 | public void run() throws Exception { 62 | final var options = new DecompileOptions(); 63 | final var ifc = new DecompInterface(); 64 | 65 | ifc.setOptions(options); 66 | ifc.openProgram(currentProgram); 67 | ifc.setSimplificationStyle("decompile"); 68 | 69 | final var curFunction = getFunctionContaining(currentAddress); 70 | final var res = ifc.decompileFunction(curFunction, 30, monitor); 71 | 72 | final var highFunction = res.getHighFunction(); 73 | if (highFunction == null) { 74 | throw new Exception("Cannot get high function"); 75 | } 76 | 77 | final var pcodeOps = highFunction.getPcodeOps(); 78 | while (pcodeOps.hasNext()) { 79 | final var pcodeElem = pcodeOps.next(); 80 | final var opcode = pcodeElem.getOpcode(); 81 | if ((opcode != PcodeOp.CALL) && (opcode != PcodeOp.CALLIND)) { 82 | continue; 83 | } 84 | 85 | VarnodeAST vn = (VarnodeAST) pcodeElem.getInput(0); 86 | if (isTargetFunction(vn, "GetProcAddress")) { 87 | println("GetProcAddress is found"); 88 | final var procName = findProcName(pcodeElem); 89 | printf("Second argument is %s\n", procName); 90 | 91 | Varnode outVarnode = pcodeElem.getOutput(); 92 | final var outVarnodeHighVariable = outVarnode.getHigh(); 93 | if (outVarnodeHighVariable == null) { 94 | println("Cannot find high variable"); 95 | break; 96 | } 97 | 98 | final var highVariable = outVarnodeHighVariable.getSymbol(); 99 | if (highVariable != null) { 100 | HighFunctionDBUtil.updateDBVariable( 101 | highVariable, "pfn" + procName, null, SourceType.USER_DEFINED); 102 | } else { 103 | println("Cannot get high symbol"); 104 | } 105 | } 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /ghidra_scripts/ModifyDecompileResultFromApiHash.java: -------------------------------------------------------------------------------- 1 | // @author Ko Nakagawa 2 | // @category PCode 3 | // @keybinding 4 | // @menupath 5 | // @toolbar 6 | 7 | import com.google.gson.Gson; 8 | import ghidra.app.decompiler.DecompInterface; 9 | import ghidra.app.decompiler.DecompileOptions; 10 | import ghidra.app.decompiler.DecompileResults; 11 | import ghidra.app.script.GhidraScript; 12 | import ghidra.program.model.listing.*; 13 | import ghidra.program.model.pcode.*; 14 | import ghidra.program.model.symbol.*; 15 | import java.io.File; 16 | import java.nio.file.Files; 17 | import java.util.HashMap; 18 | 19 | public class ModifyDecompileResultFromApiHash extends GhidraScript { 20 | private static class HashToName { 21 | public long hash; 22 | public String name; 23 | 24 | private static HashMap ConvertToHashMap(HashToName[] hashToApiNames) { 25 | HashMap hashMap = new HashMap(); 26 | for (HashToName hashToApiName : hashToApiNames) { 27 | hashMap.put(hashToApiName.hash, hashToApiName.name); 28 | } 29 | return hashMap; 30 | } 31 | } 32 | 33 | private String getFunctionNameFromPcode(PcodeOpAST pcodeAst) { 34 | final var varNode = pcodeAst.getInput(0); 35 | 36 | if (!varNode.isAddress()) return null; 37 | 38 | final var addr = varNode.getAddress(); 39 | if (addr == null) { 40 | System.err.println("Cannot get function name address"); 41 | return null; 42 | } 43 | 44 | final var sym = getSymbolAt(addr); // for API externally defined 45 | if (sym != null) { 46 | return sym.toString(); 47 | } 48 | 49 | final var func = getFunctionAt(addr); 50 | if (func != null) { 51 | return func.toString(); 52 | } 53 | 54 | return null; 55 | } 56 | 57 | private DecompileResults decompileCurrentFunction() { 58 | final Function curFunction = getFunctionContaining(currentAddress); 59 | final var options = new DecompileOptions(); 60 | final var ifc = new DecompInterface(); 61 | ifc.setOptions(options); 62 | ifc.openProgram(currentProgram); 63 | ifc.setSimplificationStyle("decompile"); 64 | return ifc.decompileFunction(curFunction, 30, monitor); 65 | } 66 | 67 | public void run() throws Exception { 68 | final File hashDbFile = askFile("DB for API hash values", "import"); 69 | if (!hashDbFile.exists()) { 70 | System.err.println("Hash DB file does not exists"); 71 | return; 72 | } 73 | 74 | final String targetFunction = 75 | askString("Type the function name", "Function resolving API address dynamically"); 76 | 77 | final String hashToNameRaw = Files.readString(hashDbFile.toPath()); 78 | final HashToName[] hashToName = new Gson().fromJson(hashToNameRaw, HashToName[].class); 79 | if (hashToName == null) { 80 | System.err.println("Cannot load hash db"); 81 | return; 82 | } 83 | final var hashToNameMap = HashToName.ConvertToHashMap(hashToName); 84 | 85 | var curFuncDecompileResults = decompileCurrentFunction(); 86 | 87 | final HighFunction highFunction = curFuncDecompileResults.getHighFunction(); 88 | if (highFunction == null) { 89 | System.err.println("Cannot get high function"); 90 | return; 91 | } 92 | 93 | // iterate for all pcode operations in current function 94 | final var iterPcodeOps = highFunction.getPcodeOps(); 95 | while (iterPcodeOps.hasNext()) { 96 | final var pcode = iterPcodeOps.next(); 97 | final var opcode = pcode.getOpcode(); 98 | 99 | if (opcode != PcodeOp.CALL) continue; 100 | 101 | final var apiResolveFncName = getFunctionNameFromPcode(pcode); 102 | if (apiResolveFncName == null) { 103 | System.err.println("Cannot get function name"); 104 | continue; 105 | } 106 | 107 | if (!apiResolveFncName.contains(targetFunction)) { 108 | System.err.println(apiResolveFncName + " is not a target function. Skipping"); 109 | continue; 110 | } 111 | 112 | final var dllHash = Long.valueOf(pcode.getInput(1).getOffset()); 113 | final var fncHash = Long.valueOf(pcode.getInput(2).getOffset()); 114 | final var dllName = hashToNameMap.get(dllHash); 115 | final var fncName = hashToNameMap.get(fncHash); 116 | 117 | if (dllName != null && fncName != null) { 118 | final var apiName = dllName.replace('.', '_') + "_" + fncName; 119 | 120 | final VarnodeAST outVarnode = (VarnodeAST) pcode.getOutput(); 121 | final HighVariable outHighVariable = outVarnode.getHigh(); 122 | if (outHighVariable == null) { 123 | System.err.println("Cannot get HighVariable"); 124 | continue; 125 | } 126 | 127 | final HighSymbol outHighSymbol = outHighVariable.getSymbol(); 128 | if (outHighSymbol == null) { 129 | System.err.println("Cannot get HighSymbol"); 130 | continue; 131 | } 132 | 133 | HighFunctionDBUtil.updateDBVariable(outHighSymbol, apiName, null, SourceType.USER_DEFINED); 134 | println("Update variable..."); 135 | } else { 136 | if (dllName == null) { 137 | System.err.println( 138 | "Cannot find name corresponding to the hash value (" 139 | + Long.toHexString(dllHash) 140 | + ")"); 141 | } 142 | if (fncName == null) { 143 | System.err.println( 144 | "Cannot find name corresponding to the hash value (" 145 | + Long.toHexString(fncHash) 146 | + ")"); 147 | } 148 | } 149 | } 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Practical P-Code examples 2 | 3 | ## Introduction 4 | 5 | P-Code is an intermediate representation used in Ghidra to describe the behavior of various processors. 6 | During the decompilation process, Ghidra converts machine code into P-Code, applies various optimizations, performs control flow structuring, and finally emits pseudo-C source code. 7 | The most important point here is that analysis can be made processor-independent by describing processor behavior with a common intermediate representation. This enables the support of the Ghidra decompilation for various processors. 8 | 9 | The use of intermediate representations is also attractive for reverse engineers because it enables a processor-independent analysis (for example, [Alexei Bluazel's excellent article](https://www.riverloopsecurity.com/blog/2019/05/pcode/) shows the data-flow analysis using P-Code). However, to my best of knowledge, there are few examples to utilize the P-Code for automating reverse-engineering tasks. 10 | 11 | This repository gathers the practical P-Code examples. I'm currently exploring the possibilities of P-Code, so there are just a few of the use cases here. If you know other interesting use-cases, pull-requests are welcome. 12 | 13 | ## About the sample scripts included in this repository 14 | 15 | ### [ModifyDecompileResultFromFunctionName.java](./ghidra_scripts/ModifyDecompileResultFromFunctionName.java) 16 | 17 | The first example is the automatic renaming function pointer variables that are dynamically resolved at run-time. Resolving Win32 API addresses dynamically through `GetProcAddress` is commonly used to hide the imported APIs from static analysis tools. Here, I show a code snippet of [sample code](https://github.com/kohnakagawa/PracticalPCode/blob/main/SampleCode/ResolveByAPIName/Main.c#L42-L54) to illustrate this. 18 | 19 | ```c 20 | // NOTE: 21 | // The following Win32 API addresses are stored as global variables 22 | // Win32 API functions are called through these global variables 23 | HMODULE kernel32Base = LoadLibraryA("kernel32.dll"); 24 | if (kernel32Base) { 25 | createProcessA = (pfnCreateProcessA)GetProcAddress(kernel32Base, "CreateProcessA"); 26 | getModuleFileNameA = (pfnGetModuleFileNameA)GetProcAddress(kernel32Base, "GetModuleFileNameA"); 27 | createThread = (pfnCreateThread)GetProcAddress(kernel32Base, "CreateThread"); 28 | } 29 | HMODULE user32Base = LoadLibraryA("user32.dll"); 30 | if (user32Base) { 31 | messageBoxA = (pfnMessageBoxA)GetProcAddress(user32Base, "MessageBoxA"); 32 | } 33 | ``` 34 | 35 | The Ghidra raw decompile result of the above code is as follows. The global variables (e.g., `DAT_14000c178`, `DAT_14000c180`, and `DAT_14000c188`) hold the Win32 API addresses. You will want to rename these global variables to more readable names such as `pfnCreateProcessA`. Of course, you can rewrite each one manually, but this is a boring task :( 36 | 37 | ![Ghidra raw decompile result](./assets/ghidra_raw_decompile_result.png) 38 | 39 | So, let's automate it. 40 | 41 | The [ModifyDecompileResultFromFunctionName.java](./ghidra_scripts/ModifyDecompileResultFromFunctionName.java) script shows how to automate this task by analyzing the P-Code of the function that contains the above process. After running this script, you can see the following refined decompile result. 42 | 43 | ![Ghidra refined decompile result](./assets/ghidra_refined_decompile_result.png) 44 | 45 | **Steps to run the script** 46 | 47 | 1. Build [ResolveByAPIName](https://github.com/kohnakagawa/PracticalPCode/tree/main/SampleCode/ResolveByAPIName) with MSVC 2019 Debug configuration 48 | 1. Open the `ResolveByAPIName.exe` with Ghidra 49 | 1. Go to the `FUN_1400014d0` function 50 | 1. Run the script 51 | 52 | ### [ModifyDecompileResultFromApiHash.java](./ghidra_scripts/ModifyDecompileResultFromApiHash.java) 53 | 54 | The second example is similar to the first example, but APIs are resolved dynamically through API hash values, not API names. This technique is used by malware (e.g., [EMOTET](https://www.netscout.com/blog/asert/emotet-whats-changed)) to obfuscate the APIs to use. Here is a code snippet of [sample code](https://github.com/kohnakagawa/PracticalPCode/blob/main/SampleCode/ResolveByAPIHash/Main.c#L196-L217) to illustrate this. 55 | 56 | ```c 57 | #define USER32_DLL_HASH 0x1031956f 58 | #define MESSAGE_BOXA_HASH 0x1545e26d 59 | 60 | #define NT_DLL_HASH 0xdf956ba6 61 | #define NT_ALLOCATE_VIRTUAL_MEMORY_HASH 0x9b8819a3 62 | #define NT_FLUSH_INSTRUCTION_CACHE_HASH 0x55c29a60 63 | #define LDR_LOAD_DLL_HASH 0x7f2584c4 64 | #define RTL_UNICODE_STRING_TO_ANSI_STRING_HASH 0xedb43455 65 | #define RTL_ANSI_STRING_TO_UNICODE_STRING_HASH 0x1c4f5b64 66 | #define RTL_INIT_ANSI_STRING_HASH 0x41ebe619 67 | #define RTL_FREE_UNICODE_STRING 0x01554596 68 | 69 | void resolve_all_apis() { 70 | fnMessageBoxA = resolve_api_addr(USER32_DLL_HASH, MESSAGE_BOXA_HASH); 71 | fnNtAlloateVirtualMemory = resolve_api_addr(NT_DLL_HASH, NT_ALLOCATE_VIRTUAL_MEMORY_HASH); 72 | fnNtFlushInstructionCache = resolve_api_addr(NT_DLL_HASH, NT_FLUSH_INSTRUCTION_CACHE_HASH); 73 | fnLdrLoadDll = resolve_api_addr(NT_DLL_HASH, LDR_LOAD_DLL_HASH); 74 | fnRtlUnicodeStringToAnsiString = resolve_api_addr(NT_DLL_HASH, RTL_UNICODE_STRING_TO_ANSI_STRING_HASH); 75 | fnRtlAnsiStringToUnicodeString = resolve_api_addr(NT_DLL_HASH, RTL_ANSI_STRING_TO_UNICODE_STRING_HASH); 76 | fnRtlInitAnsiString = resolve_api_addr(NT_DLL_HASH, RTL_INIT_ANSI_STRING_HASH); 77 | fnRtlFreeUnicodeString = resolve_api_addr(NT_DLL_HASH, RTL_FREE_UNICODE_STRING); 78 | } 79 | ``` 80 | 81 | The raw Ghidra decompile result is as follows. 82 | 83 | ![Ghidra raw decompile result](./assets/ghidra_raw_decompile_result2.png) 84 | 85 | As in the first example, let's rename the global variable names appropriately via P-Code. 86 | 87 | Let's assume that the algorithm for hash computation is already known, and we have [the list of API and DLL names](./ghidra_scripts/hash_db.json) that correspond to each hash. Using this database, [ModifyDecompileResultFromApiHash.java](./ghidra_scripts/ModifyDecompileResultFromApiHash.java) gets the hash values of the arguments of the `resolve_api` function, extracts the API and DLL names from DB, and finally modifies the global variables. After running this script, you can see the refined decompile result as the following figure. 88 | 89 | ![Ghidra refined decompile result](./assets/ghidra_refined_decompile_result2.png) 90 | 91 | **Steps to run the script** 92 | 93 | 1. Build [ResolveByAPIHash](https://github.com/kohnakagawa/PracticalPCode/tree/main/SampleCode/ResolveByAPIHash) with MSVC 2019 Debug configuration 94 | 1. Open the `ResolveByAPIHash.exe` with Ghidra 95 | 1. Go to the `FUN_140001960` function 96 | 1. `thunk_FUN_140001a80` is the function that resolves Win32 API addresses through hash values. Rename this function name to `resolve_api` 97 | 1. Run the script, and specify [hash_db.json](./ghidra_scripts/hash_db.json) as "DB for API hash values" and type `resolve_api` as "Function resolving API address dynamically". 98 | 99 | ## Author 100 | 101 | Koh M. Nakagawa 102 | 103 | ## LICENSE 104 | 105 | MIT License 106 | -------------------------------------------------------------------------------- /SampleCode/ResolveByAPIHash/ResolveByAPIHash.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 16.0 23 | Win32Proj 24 | {a3e94d84-3c04-4678-9130-ed800e88c31c} 25 | ResolveByAPIHash 26 | 10.0 27 | 28 | 29 | 30 | Application 31 | true 32 | v142 33 | Unicode 34 | 35 | 36 | Application 37 | false 38 | v142 39 | true 40 | Unicode 41 | 42 | 43 | Application 44 | true 45 | v142 46 | Unicode 47 | 48 | 49 | Application 50 | false 51 | v142 52 | true 53 | Unicode 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | true 75 | 76 | 77 | false 78 | 79 | 80 | true 81 | 82 | 83 | false 84 | 85 | 86 | 87 | Level3 88 | true 89 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 90 | true 91 | 92 | 93 | Console 94 | false 95 | 96 | 97 | 98 | 99 | Level3 100 | true 101 | true 102 | true 103 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 104 | true 105 | 106 | 107 | Console 108 | true 109 | true 110 | false 111 | 112 | 113 | 114 | 115 | Level3 116 | true 117 | _DEBUG;_CONSOLE;%(PreprocessorDefinitions) 118 | true 119 | 120 | 121 | Console 122 | false 123 | 124 | 125 | 126 | 127 | Level3 128 | true 129 | true 130 | true 131 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 132 | true 133 | 134 | 135 | Console 136 | true 137 | true 138 | false 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | -------------------------------------------------------------------------------- /SampleCode/ResolveByAPIName/ResolveByAPIName.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 16.0 23 | Win32Proj 24 | {cc2c9e4c-c3c2-44b9-a2cc-67cc9a4297b0} 25 | GetProcAddress 26 | 10.0 27 | ResolveByAPIName 28 | 29 | 30 | 31 | Application 32 | true 33 | v142 34 | Unicode 35 | 36 | 37 | Application 38 | false 39 | v142 40 | true 41 | Unicode 42 | 43 | 44 | Application 45 | true 46 | v142 47 | Unicode 48 | 49 | 50 | Application 51 | false 52 | v142 53 | true 54 | Unicode 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | true 76 | 77 | 78 | false 79 | 80 | 81 | true 82 | 83 | 84 | false 85 | 86 | 87 | 88 | Level3 89 | true 90 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 91 | true 92 | 93 | 94 | Console 95 | true 96 | 97 | 98 | 99 | 100 | Level3 101 | true 102 | true 103 | true 104 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 105 | true 106 | 107 | 108 | Console 109 | true 110 | true 111 | true 112 | 113 | 114 | 115 | 116 | Level3 117 | true 118 | _DEBUG;_CONSOLE;%(PreprocessorDefinitions) 119 | true 120 | 121 | 122 | Console 123 | false 124 | 125 | 126 | 127 | 128 | Level3 129 | true 130 | true 131 | true 132 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 133 | true 134 | 135 | 136 | Console 137 | true 138 | true 139 | true 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Ww][Ii][Nn]32/ 27 | [Aa][Rr][Mm]/ 28 | [Aa][Rr][Mm]64/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Ll]og/ 33 | [Ll]ogs/ 34 | 35 | # Visual Studio 2015/2017 cache/options directory 36 | .vs/ 37 | # Uncomment if you have tasks that create the project's static files in wwwroot 38 | #wwwroot/ 39 | 40 | # Visual Studio 2017 auto generated files 41 | Generated\ Files/ 42 | 43 | # MSTest test Results 44 | [Tt]est[Rr]esult*/ 45 | [Bb]uild[Ll]og.* 46 | 47 | # NUnit 48 | *.VisualState.xml 49 | TestResult.xml 50 | nunit-*.xml 51 | 52 | # Build Results of an ATL Project 53 | [Dd]ebugPS/ 54 | [Rr]eleasePS/ 55 | dlldata.c 56 | 57 | # Benchmark Results 58 | BenchmarkDotNet.Artifacts/ 59 | 60 | # .NET Core 61 | project.lock.json 62 | project.fragment.lock.json 63 | artifacts/ 64 | 65 | # ASP.NET Scaffolding 66 | ScaffoldingReadMe.txt 67 | 68 | # StyleCop 69 | StyleCopReport.xml 70 | 71 | # Files built by Visual Studio 72 | *_i.c 73 | *_p.c 74 | *_h.h 75 | *.ilk 76 | *.meta 77 | *.obj 78 | *.iobj 79 | *.pch 80 | *.pdb 81 | *.ipdb 82 | *.pgc 83 | *.pgd 84 | *.rsp 85 | *.sbr 86 | *.tlb 87 | *.tli 88 | *.tlh 89 | *.tmp 90 | *.tmp_proj 91 | *_wpftmp.csproj 92 | *.log 93 | *.vspscc 94 | *.vssscc 95 | .builds 96 | *.pidb 97 | *.svclog 98 | *.scc 99 | 100 | # Chutzpah Test files 101 | _Chutzpah* 102 | 103 | # Visual C++ cache files 104 | ipch/ 105 | *.aps 106 | *.ncb 107 | *.opendb 108 | *.opensdf 109 | *.sdf 110 | *.cachefile 111 | *.VC.db 112 | *.VC.VC.opendb 113 | 114 | # Visual Studio profiler 115 | *.psess 116 | *.vsp 117 | *.vspx 118 | *.sap 119 | 120 | # Visual Studio Trace Files 121 | *.e2e 122 | 123 | # TFS 2012 Local Workspace 124 | $tf/ 125 | 126 | # Guidance Automation Toolkit 127 | *.gpState 128 | 129 | # ReSharper is a .NET coding add-in 130 | _ReSharper*/ 131 | *.[Rr]e[Ss]harper 132 | *.DotSettings.user 133 | 134 | # TeamCity is a build add-in 135 | _TeamCity* 136 | 137 | # DotCover is a Code Coverage Tool 138 | *.dotCover 139 | 140 | # AxoCover is a Code Coverage Tool 141 | .axoCover/* 142 | !.axoCover/settings.json 143 | 144 | # Coverlet is a free, cross platform Code Coverage Tool 145 | coverage*.json 146 | coverage*.xml 147 | coverage*.info 148 | 149 | # Visual Studio code coverage results 150 | *.coverage 151 | *.coveragexml 152 | 153 | # NCrunch 154 | _NCrunch_* 155 | .*crunch*.local.xml 156 | nCrunchTemp_* 157 | 158 | # MightyMoose 159 | *.mm.* 160 | AutoTest.Net/ 161 | 162 | # Web workbench (sass) 163 | .sass-cache/ 164 | 165 | # Installshield output folder 166 | [Ee]xpress/ 167 | 168 | # DocProject is a documentation generator add-in 169 | DocProject/buildhelp/ 170 | DocProject/Help/*.HxT 171 | DocProject/Help/*.HxC 172 | DocProject/Help/*.hhc 173 | DocProject/Help/*.hhk 174 | DocProject/Help/*.hhp 175 | DocProject/Help/Html2 176 | DocProject/Help/html 177 | 178 | # Click-Once directory 179 | publish/ 180 | 181 | # Publish Web Output 182 | *.[Pp]ublish.xml 183 | *.azurePubxml 184 | # Note: Comment the next line if you want to checkin your web deploy settings, 185 | # but database connection strings (with potential passwords) will be unencrypted 186 | *.pubxml 187 | *.publishproj 188 | 189 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 190 | # checkin your Azure Web App publish settings, but sensitive information contained 191 | # in these scripts will be unencrypted 192 | PublishScripts/ 193 | 194 | # NuGet Packages 195 | *.nupkg 196 | # NuGet Symbol Packages 197 | *.snupkg 198 | # The packages folder can be ignored because of Package Restore 199 | **/[Pp]ackages/* 200 | # except build/, which is used as an MSBuild target. 201 | !**/[Pp]ackages/build/ 202 | # Uncomment if necessary however generally it will be regenerated when needed 203 | #!**/[Pp]ackages/repositories.config 204 | # NuGet v3's project.json files produces more ignorable files 205 | *.nuget.props 206 | *.nuget.targets 207 | 208 | # Microsoft Azure Build Output 209 | csx/ 210 | *.build.csdef 211 | 212 | # Microsoft Azure Emulator 213 | ecf/ 214 | rcf/ 215 | 216 | # Windows Store app package directories and files 217 | AppPackages/ 218 | BundleArtifacts/ 219 | Package.StoreAssociation.xml 220 | _pkginfo.txt 221 | *.appx 222 | *.appxbundle 223 | *.appxupload 224 | 225 | # Visual Studio cache files 226 | # files ending in .cache can be ignored 227 | *.[Cc]ache 228 | # but keep track of directories ending in .cache 229 | !?*.[Cc]ache/ 230 | 231 | # Others 232 | ClientBin/ 233 | ~$* 234 | *~ 235 | *.dbmdl 236 | *.dbproj.schemaview 237 | *.jfm 238 | *.pfx 239 | *.publishsettings 240 | orleans.codegen.cs 241 | 242 | # Including strong name files can present a security risk 243 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 244 | #*.snk 245 | 246 | # Since there are multiple workflows, uncomment next line to ignore bower_components 247 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 248 | #bower_components/ 249 | 250 | # RIA/Silverlight projects 251 | Generated_Code/ 252 | 253 | # Backup & report files from converting an old project file 254 | # to a newer Visual Studio version. Backup files are not needed, 255 | # because we have git ;-) 256 | _UpgradeReport_Files/ 257 | Backup*/ 258 | UpgradeLog*.XML 259 | UpgradeLog*.htm 260 | ServiceFabricBackup/ 261 | *.rptproj.bak 262 | 263 | # SQL Server files 264 | *.mdf 265 | *.ldf 266 | *.ndf 267 | 268 | # Business Intelligence projects 269 | *.rdl.data 270 | *.bim.layout 271 | *.bim_*.settings 272 | *.rptproj.rsuser 273 | *- [Bb]ackup.rdl 274 | *- [Bb]ackup ([0-9]).rdl 275 | *- [Bb]ackup ([0-9][0-9]).rdl 276 | 277 | # Microsoft Fakes 278 | FakesAssemblies/ 279 | 280 | # GhostDoc plugin setting file 281 | *.GhostDoc.xml 282 | 283 | # Node.js Tools for Visual Studio 284 | .ntvs_analysis.dat 285 | node_modules/ 286 | 287 | # Visual Studio 6 build log 288 | *.plg 289 | 290 | # Visual Studio 6 workspace options file 291 | *.opt 292 | 293 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 294 | *.vbw 295 | 296 | # Visual Studio LightSwitch build output 297 | **/*.HTMLClient/GeneratedArtifacts 298 | **/*.DesktopClient/GeneratedArtifacts 299 | **/*.DesktopClient/ModelManifest.xml 300 | **/*.Server/GeneratedArtifacts 301 | **/*.Server/ModelManifest.xml 302 | _Pvt_Extensions 303 | 304 | # Paket dependency manager 305 | .paket/paket.exe 306 | paket-files/ 307 | 308 | # FAKE - F# Make 309 | .fake/ 310 | 311 | # CodeRush personal settings 312 | .cr/personal 313 | 314 | # Python Tools for Visual Studio (PTVS) 315 | __pycache__/ 316 | *.pyc 317 | 318 | # Cake - Uncomment if you are using it 319 | # tools/** 320 | # !tools/packages.config 321 | 322 | # Tabs Studio 323 | *.tss 324 | 325 | # Telerik's JustMock configuration file 326 | *.jmconfig 327 | 328 | # BizTalk build output 329 | *.btp.cs 330 | *.btm.cs 331 | *.odx.cs 332 | *.xsd.cs 333 | 334 | # OpenCover UI analysis results 335 | OpenCover/ 336 | 337 | # Azure Stream Analytics local run output 338 | ASALocalRun/ 339 | 340 | # MSBuild Binary and Structured Log 341 | *.binlog 342 | 343 | # NVidia Nsight GPU debugger configuration file 344 | *.nvuser 345 | 346 | # MFractors (Xamarin productivity tool) working folder 347 | .mfractor/ 348 | 349 | # Local History for Visual Studio 350 | .localhistory/ 351 | 352 | # BeatPulse healthcheck temp database 353 | healthchecksdb 354 | 355 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 356 | MigrationBackup/ 357 | 358 | # Ionide (cross platform F# VS Code tools) working folder 359 | .ionide/ 360 | 361 | # Fody - auto-generated XML schema 362 | FodyWeavers.xsd 363 | -------------------------------------------------------------------------------- /SampleCode/ResolveByAPIHash/Main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | uint32_t calc_dll_hash(const char* dll_name, 10 | uint32_t len) { 11 | uint32_t hash = 0; 12 | for (uint32_t i = 0; i < len; i++) { 13 | hash = _rotr(hash, 0xd); 14 | if (dll_name[i] > 0x60) { // to uppercase 15 | hash -= 0x20; 16 | } 17 | hash += (uint32_t)dll_name[i]; 18 | } 19 | return hash; 20 | } 21 | 22 | uint32_t calc_fnc_hash(const char* fnc_name, 23 | uint32_t len) { 24 | uint32_t hash = 0; 25 | for (uint32_t i = 0; i < len; i++) { 26 | hash = _rotr(hash, 0xd) + (uint32_t)fnc_name[i]; 27 | } 28 | return hash; 29 | } 30 | 31 | void show_hashes(const char* dll_name, const char* fnc_name) { 32 | const uint32_t dll_hash = calc_dll_hash(dll_name, (uint32_t)strlen(dll_name) + 1); 33 | const uint32_t fnc_hash = calc_fnc_hash(fnc_name, (uint32_t)strlen(fnc_name) + 1); 34 | printf("%x %x\n", dll_hash, fnc_hash); 35 | } 36 | 37 | #define RVA2VA(type, base, offset) (type)((ULONG_PTR)base + offset) 38 | 39 | LPVOID search_exp_function(LPVOID dll_base, DWORD dll_hash_target, DWORD fnc_hash_target) { 40 | PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)dll_base; 41 | PIMAGE_NT_HEADERS nt = RVA2VA(PIMAGE_NT_HEADERS, dll_base, dos->e_lfanew); 42 | PIMAGE_DATA_DIRECTORY dir = nt->OptionalHeader.DataDirectory; 43 | DWORD rva = dir[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress; 44 | if (rva == 0) return NULL; 45 | PIMAGE_EXPORT_DIRECTORY exp = (PIMAGE_EXPORT_DIRECTORY) RVA2VA(ULONG_PTR, dll_base, rva); 46 | DWORD cnt = exp->NumberOfNames; 47 | if (cnt == 0) return NULL; 48 | PDWORD adr = RVA2VA(PDWORD, dll_base, exp->AddressOfFunctions); 49 | PDWORD sym = RVA2VA(PDWORD, dll_base, exp->AddressOfNames); 50 | PWORD ord = RVA2VA(PWORD, dll_base, exp->AddressOfNameOrdinals); 51 | PCHAR dll = RVA2VA(PCHAR, dll_base, exp->Name); 52 | DWORD dll_hash = calc_dll_hash(dll, (uint32_t)strlen(dll) + 1); 53 | if (dll_hash != dll_hash_target) return NULL; 54 | 55 | LPVOID api_addr = NULL; 56 | do { 57 | PCHAR api = RVA2VA(PCHAR, dll_base, sym[cnt-1]); 58 | if (calc_fnc_hash(api, (uint32_t)strlen(api) + 1) == fnc_hash_target) { 59 | api_addr = RVA2VA(LPVOID, dll_base, adr[ord[cnt-1]]); 60 | return api_addr; 61 | } 62 | } while (--cnt && api_addr==0); 63 | return api_addr; 64 | } 65 | 66 | typedef struct { 67 | ULONG Length; 68 | BOOLEAN Initialized; 69 | PVOID SsHandle; 70 | LIST_ENTRY InLoadOrderModuleList; 71 | LIST_ENTRY InMemoryOrderModuleList; 72 | LIST_ENTRY InInitializationOrderModuleList; 73 | } MY_PEB_LDR_DATA; 74 | 75 | typedef struct { 76 | LIST_ENTRY InLoadOrderLinks; 77 | LIST_ENTRY InMemoryOrderLinks; 78 | LIST_ENTRY InInitializationOrderLinks; 79 | PVOID DllBase; 80 | PVOID EntryPoint; 81 | ULONG SizeOfImage; 82 | UNICODE_STRING FullDllName; 83 | UNICODE_STRING BaseDllName; 84 | } MY_LDR_DATA_TABLE_ENTRY; 85 | 86 | LPVOID resolve_api_addr(DWORD dll_hash, DWORD fnc_hash) { 87 | LPVOID api_addr = NULL; 88 | PPEB peb = NtCurrentTeb()->ProcessEnvironmentBlock; 89 | MY_PEB_LDR_DATA* ldr = (MY_PEB_LDR_DATA*)(peb->Ldr); 90 | for (MY_LDR_DATA_TABLE_ENTRY* dte = (MY_LDR_DATA_TABLE_ENTRY*)ldr->InLoadOrderModuleList.Flink; 91 | dte->DllBase != 0 && api_addr == NULL; 92 | dte = (MY_LDR_DATA_TABLE_ENTRY*)dte->InLoadOrderLinks.Flink) { 93 | LPVOID dll_base = dte->DllBase; 94 | api_addr = search_exp_function(dll_base, dll_hash, fnc_hash); 95 | } 96 | return api_addr; 97 | } 98 | 99 | #define KERNEL32_DLL_HASH 0x50bb715e 100 | #define GET_PROC_ADDRESS_HASH 0xe553e06f 101 | 102 | void test_resolve_api_addr(const char* dll_name, 103 | const char* fnc_name) { 104 | HMODULE dll_base = LoadLibraryA(dll_name); 105 | LPVOID api_addr0 = NULL; 106 | if (dll_base) { 107 | api_addr0 = GetProcAddress(dll_base, fnc_name); 108 | } 109 | 110 | LPVOID api_addr1 = resolve_api_addr(calc_dll_hash(dll_name, (uint32_t)strlen(dll_name) + 1), 111 | calc_fnc_hash(fnc_name, (uint32_t)strlen(fnc_name) + 1)); 112 | 113 | if (api_addr0 == api_addr1) { 114 | puts("OK"); 115 | } 116 | else { 117 | puts("NG"); 118 | } 119 | } 120 | 121 | typedef NTSTATUS (NTAPI* pNtAlloateVirtualMemory)( 122 | HANDLE ProcessHandle, 123 | PVOID* BaseAddress, 124 | ULONG_PTR ZeroBits, 125 | PSIZE_T RegionSize, 126 | ULONG AllocationType, 127 | ULONG Protect 128 | ); 129 | pNtAlloateVirtualMemory fnNtAlloateVirtualMemory = NULL; 130 | 131 | typedef NTSTATUS (NTAPI* pNtFlushInstructionCache)( 132 | HANDLE ProcessHandle, 133 | PVOID BaseAddress, 134 | ULONG NumberOfBytesToFlush 135 | ); 136 | pNtFlushInstructionCache fnNtFlushInstructionCache = NULL; 137 | 138 | typedef NTSTATUS (NTAPI* pLdrLoadDll)( 139 | ULONGLONG PathToFile, 140 | PULONG Flags, 141 | PUNICODE_STRING ModuleFileName, 142 | PHANDLE ModuleHandle 143 | ); 144 | pLdrLoadDll fnLdrLoadDll = NULL; 145 | 146 | typedef NTSTATUS (NTAPI* pRtlUnicodeStringToAnsiString)( 147 | PANSI_STRING DestinationString, 148 | PUNICODE_STRING SourceString, 149 | BOOLEAN AllocateDestinationString 150 | ); 151 | pRtlUnicodeStringToAnsiString fnRtlUnicodeStringToAnsiString = NULL; 152 | 153 | typedef NTSTATUS (NTAPI* pRtlAnsiStringToUnicodeString)( 154 | PUNICODE_STRING DestinationString, 155 | PANSI_STRING SourceString, 156 | BOOLEAN AllocateDestinationString 157 | ); 158 | pRtlAnsiStringToUnicodeString fnRtlAnsiStringToUnicodeString = NULL; 159 | 160 | typedef void (NTAPI* pRtlInitAnsiString)( 161 | PANSI_STRING DestinationString, 162 | PSZ SourceString 163 | ); 164 | pRtlInitAnsiString fnRtlInitAnsiString = NULL; 165 | 166 | typedef void (NTAPI* pRtlFreeAnsiString)(PANSI_STRING AnsiString); 167 | pRtlFreeAnsiString fnRtlFreeAnsiString = NULL; 168 | 169 | typedef void (NTAPI* pRtlFreeUnicodeString)(PUNICODE_STRING UnicodeString); 170 | pRtlFreeUnicodeString fnRtlFreeUnicodeString = NULL; 171 | 172 | typedef NTSTATUS (NTAPI* pLdrGetProcedureAddressForCaller)( 173 | HMODULE ModuleHandle, 174 | PANSI_STRING FunctionName, 175 | WORD Oridinal, 176 | PVOID *FunctionAddress, 177 | BOOL bValue, 178 | PVOID CallbackAddress 179 | ); 180 | pLdrGetProcedureAddressForCaller fnLdrGetProcedureAddressForCaller; 181 | 182 | typedef int (WINAPI* pMessageBoxA)(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType); 183 | pMessageBoxA fnMessageBoxA = NULL; 184 | 185 | void show_all_hashes() { 186 | show_hashes("user32.dll", "MessageBoxA"); 187 | show_hashes("ntdll.dll", "NtAlloateVirtualMemory"); 188 | show_hashes("ntdll.dll", "NtFlushInstructionCache"); 189 | show_hashes("ntdll.dll", "LdrLoadDll"); 190 | show_hashes("ntdll.dll", "RtlUnicodeStringToAnsiString"); 191 | show_hashes("ntdll.dll", "RtlAnsiStringToUnicodeString"); 192 | show_hashes("ntdll.dll", "RtlInitAnsiString"); 193 | show_hashes("ntdll.dll", "RtlFreeUnicodeString"); 194 | } 195 | 196 | #define USER32_DLL_HASH 0x1031956f 197 | #define MESSAGE_BOXA_HASH 0x1545e26d 198 | 199 | #define NT_DLL_HASH 0xdf956ba6 200 | #define NT_ALLOCATE_VIRTUAL_MEMORY_HASH 0x9b8819a3 201 | #define NT_FLUSH_INSTRUCTION_CACHE_HASH 0x55c29a60 202 | #define LDR_LOAD_DLL_HASH 0x7f2584c4 203 | #define RTL_UNICODE_STRING_TO_ANSI_STRING_HASH 0xedb43455 204 | #define RTL_ANSI_STRING_TO_UNICODE_STRING_HASH 0x1c4f5b64 205 | #define RTL_INIT_ANSI_STRING_HASH 0x41ebe619 206 | #define RTL_FREE_UNICODE_STRING 0x01554596 207 | 208 | void resolve_all_apis() { 209 | fnMessageBoxA = resolve_api_addr(USER32_DLL_HASH, MESSAGE_BOXA_HASH); 210 | fnNtAlloateVirtualMemory = resolve_api_addr(NT_DLL_HASH, NT_ALLOCATE_VIRTUAL_MEMORY_HASH); 211 | fnNtFlushInstructionCache = resolve_api_addr(NT_DLL_HASH, NT_FLUSH_INSTRUCTION_CACHE_HASH); 212 | fnLdrLoadDll = resolve_api_addr(NT_DLL_HASH, LDR_LOAD_DLL_HASH); 213 | fnRtlUnicodeStringToAnsiString = resolve_api_addr(NT_DLL_HASH, RTL_UNICODE_STRING_TO_ANSI_STRING_HASH); 214 | fnRtlAnsiStringToUnicodeString = resolve_api_addr(NT_DLL_HASH, RTL_ANSI_STRING_TO_UNICODE_STRING_HASH); 215 | fnRtlInitAnsiString = resolve_api_addr(NT_DLL_HASH, RTL_INIT_ANSI_STRING_HASH); 216 | fnRtlFreeUnicodeString = resolve_api_addr(NT_DLL_HASH, RTL_FREE_UNICODE_STRING); 217 | } 218 | 219 | int main() { 220 | LoadLibraryA("user32.dll"); // to use MessageBoxA 221 | 222 | // show_all_hashes(); 223 | resolve_all_apis(); 224 | fnMessageBoxA(NULL, "Hello", "Hello", MB_OK); 225 | return EXIT_SUCCESS; 226 | } --------------------------------------------------------------------------------