├── .gitignore ├── README.md └── frida-cfg-hook.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | *.egg-info/ 23 | .installed.cfg 24 | *.egg 25 | 26 | # PyInstaller 27 | # Usually these files are written by a python script from a template 28 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 29 | *.manifest 30 | *.spec 31 | 32 | # Installer logs 33 | pip-log.txt 34 | pip-delete-this-directory.txt 35 | 36 | # Unit test / coverage reports 37 | htmlcov/ 38 | .tox/ 39 | .coverage 40 | .coverage.* 41 | .cache 42 | nosetests.xml 43 | coverage.xml 44 | *,cover 45 | 46 | # Translations 47 | *.mo 48 | *.pot 49 | 50 | # Django stuff: 51 | *.log 52 | 53 | # Sphinx documentation 54 | docs/_build/ 55 | 56 | # PyBuilder 57 | target/ 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # frida-cfg-hook 2 | This is a sample instrumentation script based on the [Frida instrumentation toolkit](http://www.frida.re/) which leverages [Control Flow Guard](http://blogs.msdn.com/b/vcblog/archive/2014/12/08/visual-studio-2015-preview-work-in-progress-security-feature.aspx) to intercept indirect calls in CFG-enabled Windows binaries. 3 | 4 | This is based on an idea by [@deroko_](https://twitter.com/deroko_), who first [implemented it in C](http://deroko.phearless.org/cfg_hook.zip). 5 | 6 | This sample instrumentation script will attach to a running process and hook the **ntdll!LdrpValidateUserCallTarget** function, and every time it's called it will log the address from which it was invoked, and the function pointer that CFG is about to validate. 7 | Hopefully you should be able to customize it to meet your needs by modifying the Javascript part of the code. 8 | 9 | *frida-cfg-hook* has been tested on 32-bit Windows 8.1 Update 3. 10 | 11 | ### Usage 12 | Just run the Python script specifying the PID or the name of the running process you want to instrument. Examples: 13 | 14 | ``` 15 | python frida-cfg-hook.py 1234 16 | ``` 17 | or 18 | ``` 19 | python frida-cfg-hook.py calc.exe 20 | ``` 21 | 22 | 23 | ### Dependencies 24 | * [pefile](https://github.com/erocarrera/pefile) 1.2.10-139 25 | * [frida](http://www.frida.re) 4.2.2 26 | -------------------------------------------------------------------------------- /frida-cfg-hook.py: -------------------------------------------------------------------------------- 1 | # Written by Francisco Falcon (@fdfalcon) 2 | # Based on an idea by deroko (@deroko_) 3 | # Tested on 32-bit Windows 8.1 Update 3 4 | # https://github.com/fdfalcon/frida-cfg-hook 5 | 6 | #Dependencies: 7 | # * pefile==1.2.10-139 8 | # * frida==4.2.2 9 | 10 | 11 | import sys 12 | import frida 13 | import pefile 14 | 15 | 16 | def get_GuardCFCheckFunctionPointer_rva(): 17 | ntdll = pefile.PE('C:\\Windows\\System32\\ntdll.dll') 18 | return ntdll.get_rva_from_offset(ntdll.DIRECTORY_ENTRY_LOAD_CONFIG.struct.get_field_absolute_offset('GuardCFCheckFunctionPointer')) 19 | 20 | 21 | #You may want to handle incoming messages from JS code here... 22 | def on_message(message, data): 23 | print "[%s] -> %s" % (message, data) 24 | 25 | 26 | def main(): 27 | if len(sys.argv) < 2: 28 | print('Usage: %s ' % sys.argv[0]) 29 | sys.exit(1) 30 | 31 | try: 32 | target_process = int(sys.argv[1]) 33 | except ValueError: 34 | target_process = sys.argv[1] 35 | 36 | guard_fptr_rva = get_GuardCFCheckFunctionPointer_rva() 37 | 38 | session = frida.attach(target_process) 39 | script = session.create_script(""" 40 | var GuardCFCheckFunctionPointer = Module.findBaseAddress('ntdll.dll').add(0x%x); 41 | console.log('GuardCFCheckFunctionPointer: ' + GuardCFCheckFunctionPointer.toString()); 42 | 43 | /* LoadConfig.GuardCFCheckFunctionPointer -> __guard_check_icall_fptr -> guard_function 44 | * When CFG is enabled, __guard_check_icall_fptr points to ntdll!LrdpValidateUserCallTarget (not exported, unfortunately). 45 | * Otherwise, __guard_check_icall_fptr points to dummy function _guard_check_icall_nop (exported as ntdll!RtlDebugPrintTimes) 46 | */ 47 | var guard_function = Memory.readPointer(Memory.readPointer(GuardCFCheckFunctionPointer)); 48 | console.log('Guard function: ' + guard_function.toString()); 49 | 50 | var nop_function = Module.findExportByName('ntdll.dll', 'RtlDebugPrintTimes'); 51 | console.log('RtlDebugPrintTimes: ' + nop_function.toString()); 52 | 53 | if (guard_function.equals(nop_function)){ 54 | console.log('[!] the instrumented program does not use Control Flow Guard!'); 55 | console.log('[!] __guard_check_icall_fptr points to dummy function _guard_check_icall_nop.'); 56 | send('no-cfg-enabled'); 57 | } 58 | else{ 59 | console.log('>> Hooking ntdll!LrdpValidateUserCallTarget...'); 60 | Interceptor.attach(guard_function, { 61 | onEnter: function(args){ 62 | console.log('[+] called from ' + this.returnAddress.sub(6).toString() + ' | validating function ptr ' + this.context.ecx); 63 | } 64 | }); 65 | } 66 | """ % (guard_fptr_rva)) 67 | script.on('message', on_message) 68 | script.load() 69 | raw_input("[!] Press at any time to detach from the instrumented program.\n\n") 70 | session.detach() 71 | 72 | 73 | if __name__ == '__main__': 74 | main() 75 | --------------------------------------------------------------------------------