Latest commit

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Practical P-Code examples

Introduction

P-Code is an intermediate representation used in Ghidra to describe the behavior of various processors. 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. 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.

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 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.

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.

About the sample scripts included in this repository

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 to illustrate this.

// NOTE:// The following Win32 API addresses are stored as global variables// Win32 API functions are called through these global variablesHMODULEkernel32Base=LoadLibraryA("kernel32.dll");
if (kernel32Base) {
createProcessA= (pfnCreateProcessA)GetProcAddress(kernel32Base, "CreateProcessA");
getModuleFileNameA= (pfnGetModuleFileNameA)GetProcAddress(kernel32Base, "GetModuleFileNameA");
createThread= (pfnCreateThread)GetProcAddress(kernel32Base, "CreateThread");
}
HMODULEuser32Base=LoadLibraryA("user32.dll");
if (user32Base) {
messageBoxA= (pfnMessageBoxA)GetProcAddress(user32Base, "MessageBoxA");
}

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 :(

Ghidra raw decompile result

So, let's automate it.

The 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.

Ghidra refined decompile result

Steps to run the script

  1. Build ResolveByAPIName with MSVC 2019 Debug configuration
  2. Open the ResolveByAPIName.exe with Ghidra
  3. Go to the FUN_1400014d0 function
  4. Run the script

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) to obfuscate the APIs to use. Here is a code snippet of sample code to illustrate this.

#defineUSER32_DLL_HASH 0x1031956f
#defineMESSAGE_BOXA_HASH 0x1545e26d
#defineNT_DLL_HASH 0xdf956ba6
#defineNT_ALLOCATE_VIRTUAL_MEMORY_HASH 0x9b8819a3
#defineNT_FLUSH_INSTRUCTION_CACHE_HASH 0x55c29a60
#defineLDR_LOAD_DLL_HASH 0x7f2584c4
#defineRTL_UNICODE_STRING_TO_ANSI_STRING_HASH 0xedb43455
#defineRTL_ANSI_STRING_TO_UNICODE_STRING_HASH 0x1c4f5b64
#defineRTL_INIT_ANSI_STRING_HASH 0x41ebe619
#defineRTL_FREE_UNICODE_STRING 0x01554596
voidresolve_all_apis() {
fnMessageBoxA=resolve_api_addr(USER32_DLL_HASH, MESSAGE_BOXA_HASH);
fnNtAlloateVirtualMemory=resolve_api_addr(NT_DLL_HASH, NT_ALLOCATE_VIRTUAL_MEMORY_HASH);
fnNtFlushInstructionCache=resolve_api_addr(NT_DLL_HASH, NT_FLUSH_INSTRUCTION_CACHE_HASH);
fnLdrLoadDll=resolve_api_addr(NT_DLL_HASH, LDR_LOAD_DLL_HASH);
fnRtlUnicodeStringToAnsiString=resolve_api_addr(NT_DLL_HASH, RTL_UNICODE_STRING_TO_ANSI_STRING_HASH);
fnRtlAnsiStringToUnicodeString=resolve_api_addr(NT_DLL_HASH, RTL_ANSI_STRING_TO_UNICODE_STRING_HASH);
fnRtlInitAnsiString=resolve_api_addr(NT_DLL_HASH, RTL_INIT_ANSI_STRING_HASH);
fnRtlFreeUnicodeString=resolve_api_addr(NT_DLL_HASH, RTL_FREE_UNICODE_STRING);
}

The raw Ghidra decompile result is as follows.

Ghidra raw decompile result

As in the first example, let's rename the global variable names appropriately via P-Code.

Let's assume that the algorithm for hash computation is already known, and we have the list of API and DLL names that correspond to each hash. Using this database, 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.

Ghidra refined decompile result

Steps to run the script

  1. Build ResolveByAPIHash with MSVC 2019 Debug configuration
  2. Open the ResolveByAPIHash.exe with Ghidra
  3. Go to the FUN_140001960 function
  4. thunk_FUN_140001a80 is the function that resolves Win32 API addresses through hash values. Rename this function name to resolve_api
  5. Run the script, and specify hash_db.json as "DB for API hash values" and type resolve_api as "Function resolving API address dynamically".

Author

Koh M. Nakagawa

LICENSE

MIT License

About

Practical P-Code examples

Topics

Resources

Stars

67 stars

Watchers

3 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Latest commit

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Practical P-Code examples

Introduction

P-Code is an intermediate representation used in Ghidra to describe the behavior of various processors. 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. 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.

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 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.

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.

About the sample scripts included in this repository

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 to illustrate this.

// NOTE:// The following Win32 API addresses are stored as global variables// Win32 API functions are called through these global variablesHMODULEkernel32Base=LoadLibraryA("kernel32.dll");
if (kernel32Base) {
createProcessA= (pfnCreateProcessA)GetProcAddress(kernel32Base, "CreateProcessA");
getModuleFileNameA= (pfnGetModuleFileNameA)GetProcAddress(kernel32Base, "GetModuleFileNameA");
createThread= (pfnCreateThread)GetProcAddress(kernel32Base, "CreateThread");
}
HMODULEuser32Base=LoadLibraryA("user32.dll");
if (user32Base) {
messageBoxA= (pfnMessageBoxA)GetProcAddress(user32Base, "MessageBoxA");
}

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 :(

Ghidra raw decompile result

So, let's automate it.

The 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.

Ghidra refined decompile result

Steps to run the script

  1. Build ResolveByAPIName with MSVC 2019 Debug configuration
  2. Open the ResolveByAPIName.exe with Ghidra
  3. Go to the FUN_1400014d0 function
  4. Run the script

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) to obfuscate the APIs to use. Here is a code snippet of sample code to illustrate this.

#defineUSER32_DLL_HASH 0x1031956f
#defineMESSAGE_BOXA_HASH 0x1545e26d
#defineNT_DLL_HASH 0xdf956ba6
#defineNT_ALLOCATE_VIRTUAL_MEMORY_HASH 0x9b8819a3
#defineNT_FLUSH_INSTRUCTION_CACHE_HASH 0x55c29a60
#defineLDR_LOAD_DLL_HASH 0x7f2584c4
#defineRTL_UNICODE_STRING_TO_ANSI_STRING_HASH 0xedb43455
#defineRTL_ANSI_STRING_TO_UNICODE_STRING_HASH 0x1c4f5b64
#defineRTL_INIT_ANSI_STRING_HASH 0x41ebe619
#defineRTL_FREE_UNICODE_STRING 0x01554596
voidresolve_all_apis() {
fnMessageBoxA=resolve_api_addr(USER32_DLL_HASH, MESSAGE_BOXA_HASH);
fnNtAlloateVirtualMemory=resolve_api_addr(NT_DLL_HASH, NT_ALLOCATE_VIRTUAL_MEMORY_HASH);
fnNtFlushInstructionCache=resolve_api_addr(NT_DLL_HASH, NT_FLUSH_INSTRUCTION_CACHE_HASH);
fnLdrLoadDll=resolve_api_addr(NT_DLL_HASH, LDR_LOAD_DLL_HASH);
fnRtlUnicodeStringToAnsiString=resolve_api_addr(NT_DLL_HASH, RTL_UNICODE_STRING_TO_ANSI_STRING_HASH);
fnRtlAnsiStringToUnicodeString=resolve_api_addr(NT_DLL_HASH, RTL_ANSI_STRING_TO_UNICODE_STRING_HASH);
fnRtlInitAnsiString=resolve_api_addr(NT_DLL_HASH, RTL_INIT_ANSI_STRING_HASH);
fnRtlFreeUnicodeString=resolve_api_addr(NT_DLL_HASH, RTL_FREE_UNICODE_STRING);
}

The raw Ghidra decompile result is as follows.

Ghidra raw decompile result

As in the first example, let's rename the global variable names appropriately via P-Code.

Let's assume that the algorithm for hash computation is already known, and we have the list of API and DLL names that correspond to each hash. Using this database, 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.

Ghidra refined decompile result

Steps to run the script

  1. Build ResolveByAPIHash with MSVC 2019 Debug configuration
  2. Open the ResolveByAPIHash.exe with Ghidra
  3. Go to the FUN_140001960 function
  4. thunk_FUN_140001a80 is the function that resolves Win32 API addresses through hash values. Rename this function name to resolve_api
  5. Run the script, and specify hash_db.json as "DB for API hash values" and type resolve_api as "Function resolving API address dynamically".

Author

Koh M. Nakagawa

LICENSE

MIT License

About

Practical P-Code examples

Topics

Resources

Stars

67 stars

Watchers

3 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Practical P-Code examples

Introduction

P-Code is an intermediate representation used in Ghidra to describe the behavior of various processors. 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. 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.

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 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.

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.

About the sample scripts included in this repository

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 to illustrate this.

// NOTE:// The following Win32 API addresses are stored as global variables// Win32 API functions are called through these global variablesHMODULEkernel32Base=LoadLibraryA("kernel32.dll");
if (kernel32Base) {
createProcessA= (pfnCreateProcessA)GetProcAddress(kernel32Base, "CreateProcessA");
getModuleFileNameA= (pfnGetModuleFileNameA)GetProcAddress(kernel32Base, "GetModuleFileNameA");
createThread= (pfnCreateThread)GetProcAddress(kernel32Base, "CreateThread");
}
HMODULEuser32Base=LoadLibraryA("user32.dll");
if (user32Base) {
messageBoxA= (pfnMessageBoxA)GetProcAddress(user32Base, "MessageBoxA");
}

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 :(

Ghidra raw decompile result

So, let's automate it.

The 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.

Ghidra refined decompile result

Steps to run the script

  1. Build ResolveByAPIName with MSVC 2019 Debug configuration
  2. Open the ResolveByAPIName.exe with Ghidra
  3. Go to the FUN_1400014d0 function
  4. Run the script

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) to obfuscate the APIs to use. Here is a code snippet of sample code to illustrate this.

#defineUSER32_DLL_HASH 0x1031956f
#defineMESSAGE_BOXA_HASH 0x1545e26d
#defineNT_DLL_HASH 0xdf956ba6
#defineNT_ALLOCATE_VIRTUAL_MEMORY_HASH 0x9b8819a3
#defineNT_FLUSH_INSTRUCTION_CACHE_HASH 0x55c29a60
#defineLDR_LOAD_DLL_HASH 0x7f2584c4
#defineRTL_UNICODE_STRING_TO_ANSI_STRING_HASH 0xedb43455
#defineRTL_ANSI_STRING_TO_UNICODE_STRING_HASH 0x1c4f5b64
#defineRTL_INIT_ANSI_STRING_HASH 0x41ebe619
#defineRTL_FREE_UNICODE_STRING 0x01554596
voidresolve_all_apis() {
fnMessageBoxA=resolve_api_addr(USER32_DLL_HASH, MESSAGE_BOXA_HASH);
fnNtAlloateVirtualMemory=resolve_api_addr(NT_DLL_HASH, NT_ALLOCATE_VIRTUAL_MEMORY_HASH);
fnNtFlushInstructionCache=resolve_api_addr(NT_DLL_HASH, NT_FLUSH_INSTRUCTION_CACHE_HASH);
fnLdrLoadDll=resolve_api_addr(NT_DLL_HASH, LDR_LOAD_DLL_HASH);
fnRtlUnicodeStringToAnsiString=resolve_api_addr(NT_DLL_HASH, RTL_UNICODE_STRING_TO_ANSI_STRING_HASH);
fnRtlAnsiStringToUnicodeString=resolve_api_addr(NT_DLL_HASH, RTL_ANSI_STRING_TO_UNICODE_STRING_HASH);
fnRtlInitAnsiString=resolve_api_addr(NT_DLL_HASH, RTL_INIT_ANSI_STRING_HASH);
fnRtlFreeUnicodeString=resolve_api_addr(NT_DLL_HASH, RTL_FREE_UNICODE_STRING);
}

The raw Ghidra decompile result is as follows.

Ghidra raw decompile result

As in the first example, let's rename the global variable names appropriately via P-Code.

Let's assume that the algorithm for hash computation is already known, and we have the list of API and DLL names that correspond to each hash. Using this database, 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.

Ghidra refined decompile result

Steps to run the script

  1. Build ResolveByAPIHash with MSVC 2019 Debug configuration
  2. Open the ResolveByAPIHash.exe with Ghidra
  3. Go to the FUN_140001960 function
  4. thunk_FUN_140001a80 is the function that resolves Win32 API addresses through hash values. Rename this function name to resolve_api
  5. Run the script, and specify hash_db.json as "DB for API hash values" and type resolve_api as "Function resolving API address dynamically".

Author

Koh M. Nakagawa

LICENSE

MIT License

About

Practical P-Code examples

Topics

Resources

Stars

67 stars

Watchers

3 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Practical P-Code examples

Introduction

P-Code is an intermediate representation used in Ghidra to describe the behavior of various processors. 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. 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.

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 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.

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.

About the sample scripts included in this repository

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 to illustrate this.

// NOTE:// The following Win32 API addresses are stored as global variables// Win32 API functions are called through these global variablesHMODULEkernel32Base=LoadLibraryA("kernel32.dll");
if (kernel32Base) {
createProcessA= (pfnCreateProcessA)GetProcAddress(kernel32Base, "CreateProcessA");
getModuleFileNameA= (pfnGetModuleFileNameA)GetProcAddress(kernel32Base, "GetModuleFileNameA");
createThread= (pfnCreateThread)GetProcAddress(kernel32Base, "CreateThread");
}
HMODULEuser32Base=LoadLibraryA("user32.dll");
if (user32Base) {
messageBoxA= (pfnMessageBoxA)GetProcAddress(user32Base, "MessageBoxA");
}

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 :(

Ghidra raw decompile result

So, let's automate it.

The 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.

Ghidra refined decompile result

Steps to run the script

  1. Build ResolveByAPIName with MSVC 2019 Debug configuration
  2. Open the ResolveByAPIName.exe with Ghidra
  3. Go to the FUN_1400014d0 function
  4. Run the script

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) to obfuscate the APIs to use. Here is a code snippet of sample code to illustrate this.

#defineUSER32_DLL_HASH 0x1031956f
#defineMESSAGE_BOXA_HASH 0x1545e26d
#defineNT_DLL_HASH 0xdf956ba6
#defineNT_ALLOCATE_VIRTUAL_MEMORY_HASH 0x9b8819a3
#defineNT_FLUSH_INSTRUCTION_CACHE_HASH 0x55c29a60
#defineLDR_LOAD_DLL_HASH 0x7f2584c4
#defineRTL_UNICODE_STRING_TO_ANSI_STRING_HASH 0xedb43455
#defineRTL_ANSI_STRING_TO_UNICODE_STRING_HASH 0x1c4f5b64
#defineRTL_INIT_ANSI_STRING_HASH 0x41ebe619
#defineRTL_FREE_UNICODE_STRING 0x01554596
voidresolve_all_apis() {
fnMessageBoxA=resolve_api_addr(USER32_DLL_HASH, MESSAGE_BOXA_HASH);
fnNtAlloateVirtualMemory=resolve_api_addr(NT_DLL_HASH, NT_ALLOCATE_VIRTUAL_MEMORY_HASH);
fnNtFlushInstructionCache=resolve_api_addr(NT_DLL_HASH, NT_FLUSH_INSTRUCTION_CACHE_HASH);
fnLdrLoadDll=resolve_api_addr(NT_DLL_HASH, LDR_LOAD_DLL_HASH);
fnRtlUnicodeStringToAnsiString=resolve_api_addr(NT_DLL_HASH, RTL_UNICODE_STRING_TO_ANSI_STRING_HASH);
fnRtlAnsiStringToUnicodeString=resolve_api_addr(NT_DLL_HASH, RTL_ANSI_STRING_TO_UNICODE_STRING_HASH);
fnRtlInitAnsiString=resolve_api_addr(NT_DLL_HASH, RTL_INIT_ANSI_STRING_HASH);
fnRtlFreeUnicodeString=resolve_api_addr(NT_DLL_HASH, RTL_FREE_UNICODE_STRING);
}

The raw Ghidra decompile result is as follows.

Ghidra raw decompile result

As in the first example, let's rename the global variable names appropriately via P-Code.

Let's assume that the algorithm for hash computation is already known, and we have the list of API and DLL names that correspond to each hash. Using this database, 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.

Ghidra refined decompile result

Steps to run the script

  1. Build ResolveByAPIHash with MSVC 2019 Debug configuration
  2. Open the ResolveByAPIHash.exe with Ghidra
  3. Go to the FUN_140001960 function
  4. thunk_FUN_140001a80 is the function that resolves Win32 API addresses through hash values. Rename this function name to resolve_api
  5. Run the script, and specify hash_db.json as "DB for API hash values" and type resolve_api as "Function resolving API address dynamically".

Author

Koh M. Nakagawa

LICENSE

MIT License

About

Practical P-Code examples

Topics

Resources

Stars

67 stars

Watchers

3 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Latest commit

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Practical P-Code examples

Introduction

P-Code is an intermediate representation used in Ghidra to describe the behavior of various processors. 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. 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.

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 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.

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.

About the sample scripts included in this repository

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 to illustrate this.

// NOTE:// The following Win32 API addresses are stored as global variables// Win32 API functions are called through these global variablesHMODULEkernel32Base=LoadLibraryA("kernel32.dll");
if (kernel32Base) {
createProcessA= (pfnCreateProcessA)GetProcAddress(kernel32Base, "CreateProcessA");
getModuleFileNameA= (pfnGetModuleFileNameA)GetProcAddress(kernel32Base, "GetModuleFileNameA");
createThread= (pfnCreateThread)GetProcAddress(kernel32Base, "CreateThread");
}
HMODULEuser32Base=LoadLibraryA("user32.dll");
if (user32Base) {
messageBoxA= (pfnMessageBoxA)GetProcAddress(user32Base, "MessageBoxA");
}

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 :(

Ghidra raw decompile result

So, let's automate it.

The 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.

Ghidra refined decompile result

Steps to run the script

  1. Build ResolveByAPIName with MSVC 2019 Debug configuration
  2. Open the ResolveByAPIName.exe with Ghidra
  3. Go to the FUN_1400014d0 function
  4. Run the script

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) to obfuscate the APIs to use. Here is a code snippet of sample code to illustrate this.

#defineUSER32_DLL_HASH 0x1031956f
#defineMESSAGE_BOXA_HASH 0x1545e26d
#defineNT_DLL_HASH 0xdf956ba6
#defineNT_ALLOCATE_VIRTUAL_MEMORY_HASH 0x9b8819a3
#defineNT_FLUSH_INSTRUCTION_CACHE_HASH 0x55c29a60
#defineLDR_LOAD_DLL_HASH 0x7f2584c4
#defineRTL_UNICODE_STRING_TO_ANSI_STRING_HASH 0xedb43455
#defineRTL_ANSI_STRING_TO_UNICODE_STRING_HASH 0x1c4f5b64
#defineRTL_INIT_ANSI_STRING_HASH 0x41ebe619
#defineRTL_FREE_UNICODE_STRING 0x01554596
voidresolve_all_apis() {
fnMessageBoxA=resolve_api_addr(USER32_DLL_HASH, MESSAGE_BOXA_HASH);
fnNtAlloateVirtualMemory=resolve_api_addr(NT_DLL_HASH, NT_ALLOCATE_VIRTUAL_MEMORY_HASH);
fnNtFlushInstructionCache=resolve_api_addr(NT_DLL_HASH, NT_FLUSH_INSTRUCTION_CACHE_HASH);
fnLdrLoadDll=resolve_api_addr(NT_DLL_HASH, LDR_LOAD_DLL_HASH);
fnRtlUnicodeStringToAnsiString=resolve_api_addr(NT_DLL_HASH, RTL_UNICODE_STRING_TO_ANSI_STRING_HASH);
fnRtlAnsiStringToUnicodeString=resolve_api_addr(NT_DLL_HASH, RTL_ANSI_STRING_TO_UNICODE_STRING_HASH);
fnRtlInitAnsiString=resolve_api_addr(NT_DLL_HASH, RTL_INIT_ANSI_STRING_HASH);
fnRtlFreeUnicodeString=resolve_api_addr(NT_DLL_HASH, RTL_FREE_UNICODE_STRING);
}

The raw Ghidra decompile result is as follows.

Ghidra raw decompile result

As in the first example, let's rename the global variable names appropriately via P-Code.

Let's assume that the algorithm for hash computation is already known, and we have the list of API and DLL names that correspond to each hash. Using this database, 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.

Ghidra refined decompile result

Steps to run the script

  1. Build ResolveByAPIHash with MSVC 2019 Debug configuration
  2. Open the ResolveByAPIHash.exe with Ghidra
  3. Go to the FUN_140001960 function
  4. thunk_FUN_140001a80 is the function that resolves Win32 API addresses through hash values. Rename this function name to resolve_api
  5. Run the script, and specify hash_db.json as "DB for API hash values" and type resolve_api as "Function resolving API address dynamically".

Author

Koh M. Nakagawa

LICENSE

MIT License

About

Practical P-Code examples

Topics

Resources

Stars

67 stars

Watchers

3 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Practical P-Code examples

Introduction

P-Code is an intermediate representation used in Ghidra to describe the behavior of various processors. 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. 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.

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 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.

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.

About the sample scripts included in this repository

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 to illustrate this.

// NOTE:// The following Win32 API addresses are stored as global variables// Win32 API functions are called through these global variablesHMODULEkernel32Base=LoadLibraryA("kernel32.dll");
if (kernel32Base) {
createProcessA= (pfnCreateProcessA)GetProcAddress(kernel32Base, "CreateProcessA");
getModuleFileNameA= (pfnGetModuleFileNameA)GetProcAddress(kernel32Base, "GetModuleFileNameA");
createThread= (pfnCreateThread)GetProcAddress(kernel32Base, "CreateThread");
}
HMODULEuser32Base=LoadLibraryA("user32.dll");
if (user32Base) {
messageBoxA= (pfnMessageBoxA)GetProcAddress(user32Base, "MessageBoxA");
}

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 :(

Ghidra raw decompile result

So, let's automate it.

The 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.

Ghidra refined decompile result

Steps to run the script

  1. Build ResolveByAPIName with MSVC 2019 Debug configuration
  2. Open the ResolveByAPIName.exe with Ghidra
  3. Go to the FUN_1400014d0 function
  4. Run the script

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) to obfuscate the APIs to use. Here is a code snippet of sample code to illustrate this.

#defineUSER32_DLL_HASH 0x1031956f
#defineMESSAGE_BOXA_HASH 0x1545e26d
#defineNT_DLL_HASH 0xdf956ba6
#defineNT_ALLOCATE_VIRTUAL_MEMORY_HASH 0x9b8819a3
#defineNT_FLUSH_INSTRUCTION_CACHE_HASH 0x55c29a60
#defineLDR_LOAD_DLL_HASH 0x7f2584c4
#defineRTL_UNICODE_STRING_TO_ANSI_STRING_HASH 0xedb43455
#defineRTL_ANSI_STRING_TO_UNICODE_STRING_HASH 0x1c4f5b64
#defineRTL_INIT_ANSI_STRING_HASH 0x41ebe619
#defineRTL_FREE_UNICODE_STRING 0x01554596
voidresolve_all_apis() {
fnMessageBoxA=resolve_api_addr(USER32_DLL_HASH, MESSAGE_BOXA_HASH);
fnNtAlloateVirtualMemory=resolve_api_addr(NT_DLL_HASH, NT_ALLOCATE_VIRTUAL_MEMORY_HASH);
fnNtFlushInstructionCache=resolve_api_addr(NT_DLL_HASH, NT_FLUSH_INSTRUCTION_CACHE_HASH);
fnLdrLoadDll=resolve_api_addr(NT_DLL_HASH, LDR_LOAD_DLL_HASH);
fnRtlUnicodeStringToAnsiString=resolve_api_addr(NT_DLL_HASH, RTL_UNICODE_STRING_TO_ANSI_STRING_HASH);
fnRtlAnsiStringToUnicodeString=resolve_api_addr(NT_DLL_HASH, RTL_ANSI_STRING_TO_UNICODE_STRING_HASH);
fnRtlInitAnsiString=resolve_api_addr(NT_DLL_HASH, RTL_INIT_ANSI_STRING_HASH);
fnRtlFreeUnicodeString=resolve_api_addr(NT_DLL_HASH, RTL_FREE_UNICODE_STRING);
}

The raw Ghidra decompile result is as follows.

Ghidra raw decompile result

As in the first example, let's rename the global variable names appropriately via P-Code.

Let's assume that the algorithm for hash computation is already known, and we have the list of API and DLL names that correspond to each hash. Using this database, 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.

Ghidra refined decompile result

Steps to run the script

  1. Build ResolveByAPIHash with MSVC 2019 Debug configuration
  2. Open the ResolveByAPIHash.exe with Ghidra
  3. Go to the FUN_140001960 function
  4. thunk_FUN_140001a80 is the function that resolves Win32 API addresses through hash values. Rename this function name to resolve_api
  5. Run the script, and specify hash_db.json as "DB for API hash values" and type resolve_api as "Function resolving API address dynamically".

Author

Koh M. Nakagawa

LICENSE

MIT License

About

Practical P-Code examples

Topics

Resources

Stars

67 stars

Watchers

3 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Practical P-Code examples

Introduction

P-Code is an intermediate representation used in Ghidra to describe the behavior of various processors. 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. 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.

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 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.

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.

About the sample scripts included in this repository

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 to illustrate this.

// NOTE:// The following Win32 API addresses are stored as global variables// Win32 API functions are called through these global variablesHMODULEkernel32Base=LoadLibraryA("kernel32.dll");
if (kernel32Base) {
createProcessA= (pfnCreateProcessA)GetProcAddress(kernel32Base, "CreateProcessA");
getModuleFileNameA= (pfnGetModuleFileNameA)GetProcAddress(kernel32Base, "GetModuleFileNameA");
createThread= (pfnCreateThread)GetProcAddress(kernel32Base, "CreateThread");
}
HMODULEuser32Base=LoadLibraryA("user32.dll");
if (user32Base) {
messageBoxA= (pfnMessageBoxA)GetProcAddress(user32Base, "MessageBoxA");
}

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 :(

Ghidra raw decompile result

So, let's automate it.

The 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.

Ghidra refined decompile result

Steps to run the script

  1. Build ResolveByAPIName with MSVC 2019 Debug configuration
  2. Open the ResolveByAPIName.exe with Ghidra
  3. Go to the FUN_1400014d0 function
  4. Run the script

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) to obfuscate the APIs to use. Here is a code snippet of sample code to illustrate this.

#defineUSER32_DLL_HASH 0x1031956f
#defineMESSAGE_BOXA_HASH 0x1545e26d
#defineNT_DLL_HASH 0xdf956ba6
#defineNT_ALLOCATE_VIRTUAL_MEMORY_HASH 0x9b8819a3
#defineNT_FLUSH_INSTRUCTION_CACHE_HASH 0x55c29a60
#defineLDR_LOAD_DLL_HASH 0x7f2584c4
#defineRTL_UNICODE_STRING_TO_ANSI_STRING_HASH 0xedb43455
#defineRTL_ANSI_STRING_TO_UNICODE_STRING_HASH 0x1c4f5b64
#defineRTL_INIT_ANSI_STRING_HASH 0x41ebe619
#defineRTL_FREE_UNICODE_STRING 0x01554596
voidresolve_all_apis() {
fnMessageBoxA=resolve_api_addr(USER32_DLL_HASH, MESSAGE_BOXA_HASH);
fnNtAlloateVirtualMemory=resolve_api_addr(NT_DLL_HASH, NT_ALLOCATE_VIRTUAL_MEMORY_HASH);
fnNtFlushInstructionCache=resolve_api_addr(NT_DLL_HASH, NT_FLUSH_INSTRUCTION_CACHE_HASH);
fnLdrLoadDll=resolve_api_addr(NT_DLL_HASH, LDR_LOAD_DLL_HASH);
fnRtlUnicodeStringToAnsiString=resolve_api_addr(NT_DLL_HASH, RTL_UNICODE_STRING_TO_ANSI_STRING_HASH);
fnRtlAnsiStringToUnicodeString=resolve_api_addr(NT_DLL_HASH, RTL_ANSI_STRING_TO_UNICODE_STRING_HASH);
fnRtlInitAnsiString=resolve_api_addr(NT_DLL_HASH, RTL_INIT_ANSI_STRING_HASH);
fnRtlFreeUnicodeString=resolve_api_addr(NT_DLL_HASH, RTL_FREE_UNICODE_STRING);
}

The raw Ghidra decompile result is as follows.

Ghidra raw decompile result

As in the first example, let's rename the global variable names appropriately via P-Code.

Let's assume that the algorithm for hash computation is already known, and we have the list of API and DLL names that correspond to each hash. Using this database, 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.

Ghidra refined decompile result

Steps to run the script

  1. Build ResolveByAPIHash with MSVC 2019 Debug configuration
  2. Open the ResolveByAPIHash.exe with Ghidra
  3. Go to the FUN_140001960 function
  4. thunk_FUN_140001a80 is the function that resolves Win32 API addresses through hash values. Rename this function name to resolve_api
  5. Run the script, and specify hash_db.json as "DB for API hash values" and type resolve_api as "Function resolving API address dynamically".

Author

Koh M. Nakagawa

LICENSE

MIT License

About

Practical P-Code examples

Topics

Resources

Stars

67 stars

Watchers

3 watching

Forks

Releases

Packages

Used by

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Latest commit

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Practical P-Code examples

Introduction

P-Code is an intermediate representation used in Ghidra to describe the behavior of various processors. 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. 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.

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 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.

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.

About the sample scripts included in this repository

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 to illustrate this.

// NOTE:// The following Win32 API addresses are stored as global variables// Win32 API functions are called through these global variablesHMODULEkernel32Base=LoadLibraryA("kernel32.dll");
if (kernel32Base) {
createProcessA= (pfnCreateProcessA)GetProcAddress(kernel32Base, "CreateProcessA");
getModuleFileNameA= (pfnGetModuleFileNameA)GetProcAddress(kernel32Base, "GetModuleFileNameA");
createThread= (pfnCreateThread)GetProcAddress(kernel32Base, "CreateThread");
}
HMODULEuser32Base=LoadLibraryA("user32.dll");
if (user32Base) {
messageBoxA= (pfnMessageBoxA)GetProcAddress(user32Base, "MessageBoxA");
}

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 :(

Ghidra raw decompile result

So, let's automate it.

The 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.

Ghidra refined decompile result

Steps to run the script

  1. Build ResolveByAPIName with MSVC 2019 Debug configuration
  2. Open the ResolveByAPIName.exe with Ghidra
  3. Go to the FUN_1400014d0 function
  4. Run the script

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) to obfuscate the APIs to use. Here is a code snippet of sample code to illustrate this.

#defineUSER32_DLL_HASH 0x1031956f
#defineMESSAGE_BOXA_HASH 0x1545e26d
#defineNT_DLL_HASH 0xdf956ba6
#defineNT_ALLOCATE_VIRTUAL_MEMORY_HASH 0x9b8819a3
#defineNT_FLUSH_INSTRUCTION_CACHE_HASH 0x55c29a60
#defineLDR_LOAD_DLL_HASH 0x7f2584c4
#defineRTL_UNICODE_STRING_TO_ANSI_STRING_HASH 0xedb43455
#defineRTL_ANSI_STRING_TO_UNICODE_STRING_HASH 0x1c4f5b64
#defineRTL_INIT_ANSI_STRING_HASH 0x41ebe619
#defineRTL_FREE_UNICODE_STRING 0x01554596
voidresolve_all_apis() {
fnMessageBoxA=resolve_api_addr(USER32_DLL_HASH, MESSAGE_BOXA_HASH);
fnNtAlloateVirtualMemory=resolve_api_addr(NT_DLL_HASH, NT_ALLOCATE_VIRTUAL_MEMORY_HASH);
fnNtFlushInstructionCache=resolve_api_addr(NT_DLL_HASH, NT_FLUSH_INSTRUCTION_CACHE_HASH);
fnLdrLoadDll=resolve_api_addr(NT_DLL_HASH, LDR_LOAD_DLL_HASH);
fnRtlUnicodeStringToAnsiString=resolve_api_addr(NT_DLL_HASH, RTL_UNICODE_STRING_TO_ANSI_STRING_HASH);
fnRtlAnsiStringToUnicodeString=resolve_api_addr(NT_DLL_HASH, RTL_ANSI_STRING_TO_UNICODE_STRING_HASH);
fnRtlInitAnsiString=resolve_api_addr(NT_DLL_HASH, RTL_INIT_ANSI_STRING_HASH);
fnRtlFreeUnicodeString=resolve_api_addr(NT_DLL_HASH, RTL_FREE_UNICODE_STRING);
}

The raw Ghidra decompile result is as follows.

Ghidra raw decompile result

As in the first example, let's rename the global variable names appropriately via P-Code.

Let's assume that the algorithm for hash computation is already known, and we have the list of API and DLL names that correspond to each hash. Using this database, 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.

Ghidra refined decompile result

Steps to run the script

  1. Build ResolveByAPIHash with MSVC 2019 Debug configuration
  2. Open the ResolveByAPIHash.exe with Ghidra
  3. Go to the FUN_140001960 function
  4. thunk_FUN_140001a80 is the function that resolves Win32 API addresses through hash values. Rename this function name to resolve_api
  5. Run the script, and specify hash_db.json as "DB for API hash values" and type resolve_api as "Function resolving API address dynamically".

Author

Koh M. Nakagawa

LICENSE

MIT License

About

Practical P-Code examples

Topics

Resources

Stars

67 stars

Watchers

3 watching

Forks

Releases

Packages

Used by

Contributors

Languages