├── 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 | 
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 | 
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 | 
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 | 
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 | }
--------------------------------------------------------------------------------