├── .gitignore ├── AFL └── test_ioctl │ ├── .gitignore │ ├── header.h │ ├── helper.h │ ├── interface.cpp │ ├── main.cpp │ ├── persistence.cpp │ ├── test_ioctl.sln │ ├── test_ioctl.vcxproj │ └── test_ioctl.vcxproj.filters ├── LICENSE.md ├── README.md ├── Tools └── FAT.vhdx.zip ├── asm_stubs64.py ├── consts.py ├── drt.py ├── helper ├── .gitignore ├── helper.inf ├── helper.sln ├── helper.vcxproj ├── helper.vcxproj.filters ├── main.c └── main.h ├── ida_dumper.py ├── instrument.py ├── pe_afl.py ├── pefile.py ├── requirements.txt ├── seh.py ├── utils.py └── winafl_example ├── afl-staticinstr.c ├── afl-staticinstr.h └── example.c /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | __pycache__/ 3 | venv/ 4 | *.pyc 5 | .venv/ 6 | -------------------------------------------------------------------------------- /AFL/test_ioctl/.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/main/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 | *.tlog 94 | *.vspscc 95 | *.vssscc 96 | .builds 97 | *.pidb 98 | *.svclog 99 | *.scc 100 | 101 | # Chutzpah Test files 102 | _Chutzpah* 103 | 104 | # Visual C++ cache files 105 | ipch/ 106 | *.aps 107 | *.ncb 108 | *.opendb 109 | *.opensdf 110 | *.sdf 111 | *.cachefile 112 | *.VC.db 113 | *.VC.VC.opendb 114 | 115 | # Visual Studio profiler 116 | *.psess 117 | *.vsp 118 | *.vspx 119 | *.sap 120 | 121 | # Visual Studio Trace Files 122 | *.e2e 123 | 124 | # TFS 2012 Local Workspace 125 | $tf/ 126 | 127 | # Guidance Automation Toolkit 128 | *.gpState 129 | 130 | # ReSharper is a .NET coding add-in 131 | _ReSharper*/ 132 | *.[Rr]e[Ss]harper 133 | *.DotSettings.user 134 | 135 | # TeamCity is a build add-in 136 | _TeamCity* 137 | 138 | # DotCover is a Code Coverage Tool 139 | *.dotCover 140 | 141 | # AxoCover is a Code Coverage Tool 142 | .axoCover/* 143 | !.axoCover/settings.json 144 | 145 | # Coverlet is a free, cross platform Code Coverage Tool 146 | coverage*.json 147 | coverage*.xml 148 | coverage*.info 149 | 150 | # Visual Studio code coverage results 151 | *.coverage 152 | *.coveragexml 153 | 154 | # NCrunch 155 | _NCrunch_* 156 | .*crunch*.local.xml 157 | nCrunchTemp_* 158 | 159 | # MightyMoose 160 | *.mm.* 161 | AutoTest.Net/ 162 | 163 | # Web workbench (sass) 164 | .sass-cache/ 165 | 166 | # Installshield output folder 167 | [Ee]xpress/ 168 | 169 | # DocProject is a documentation generator add-in 170 | DocProject/buildhelp/ 171 | DocProject/Help/*.HxT 172 | DocProject/Help/*.HxC 173 | DocProject/Help/*.hhc 174 | DocProject/Help/*.hhk 175 | DocProject/Help/*.hhp 176 | DocProject/Help/Html2 177 | DocProject/Help/html 178 | 179 | # Click-Once directory 180 | publish/ 181 | 182 | # Publish Web Output 183 | *.[Pp]ublish.xml 184 | *.azurePubxml 185 | # Note: Comment the next line if you want to checkin your web deploy settings, 186 | # but database connection strings (with potential passwords) will be unencrypted 187 | *.pubxml 188 | *.publishproj 189 | 190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 191 | # checkin your Azure Web App publish settings, but sensitive information contained 192 | # in these scripts will be unencrypted 193 | PublishScripts/ 194 | 195 | # NuGet Packages 196 | *.nupkg 197 | # NuGet Symbol Packages 198 | *.snupkg 199 | # The packages folder can be ignored because of Package Restore 200 | **/[Pp]ackages/* 201 | # except build/, which is used as an MSBuild target. 202 | !**/[Pp]ackages/build/ 203 | # Uncomment if necessary however generally it will be regenerated when needed 204 | #!**/[Pp]ackages/repositories.config 205 | # NuGet v3's project.json files produces more ignorable files 206 | *.nuget.props 207 | *.nuget.targets 208 | 209 | # Microsoft Azure Build Output 210 | csx/ 211 | *.build.csdef 212 | 213 | # Microsoft Azure Emulator 214 | ecf/ 215 | rcf/ 216 | 217 | # Windows Store app package directories and files 218 | AppPackages/ 219 | BundleArtifacts/ 220 | Package.StoreAssociation.xml 221 | _pkginfo.txt 222 | *.appx 223 | *.appxbundle 224 | *.appxupload 225 | 226 | # Visual Studio cache files 227 | # files ending in .cache can be ignored 228 | *.[Cc]ache 229 | # but keep track of directories ending in .cache 230 | !?*.[Cc]ache/ 231 | 232 | # Others 233 | ClientBin/ 234 | ~$* 235 | *~ 236 | *.dbmdl 237 | *.dbproj.schemaview 238 | *.jfm 239 | *.pfx 240 | *.publishsettings 241 | orleans.codegen.cs 242 | 243 | # Including strong name files can present a security risk 244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 245 | #*.snk 246 | 247 | # Since there are multiple workflows, uncomment next line to ignore bower_components 248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 249 | #bower_components/ 250 | 251 | # RIA/Silverlight projects 252 | Generated_Code/ 253 | 254 | # Backup & report files from converting an old project file 255 | # to a newer Visual Studio version. Backup files are not needed, 256 | # because we have git ;-) 257 | _UpgradeReport_Files/ 258 | Backup*/ 259 | UpgradeLog*.XML 260 | UpgradeLog*.htm 261 | ServiceFabricBackup/ 262 | *.rptproj.bak 263 | 264 | # SQL Server files 265 | *.mdf 266 | *.ldf 267 | *.ndf 268 | 269 | # Business Intelligence projects 270 | *.rdl.data 271 | *.bim.layout 272 | *.bim_*.settings 273 | *.rptproj.rsuser 274 | *- [Bb]ackup.rdl 275 | *- [Bb]ackup ([0-9]).rdl 276 | *- [Bb]ackup ([0-9][0-9]).rdl 277 | 278 | # Microsoft Fakes 279 | FakesAssemblies/ 280 | 281 | # GhostDoc plugin setting file 282 | *.GhostDoc.xml 283 | 284 | # Node.js Tools for Visual Studio 285 | .ntvs_analysis.dat 286 | node_modules/ 287 | 288 | # Visual Studio 6 build log 289 | *.plg 290 | 291 | # Visual Studio 6 workspace options file 292 | *.opt 293 | 294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 295 | *.vbw 296 | 297 | # Visual Studio 6 auto-generated project file (contains which files were open etc.) 298 | *.vbp 299 | 300 | # Visual Studio 6 workspace and project file (working project files containing files to include in project) 301 | *.dsw 302 | *.dsp 303 | 304 | # Visual Studio 6 technical files 305 | *.ncb 306 | *.aps 307 | 308 | # Visual Studio LightSwitch build output 309 | **/*.HTMLClient/GeneratedArtifacts 310 | **/*.DesktopClient/GeneratedArtifacts 311 | **/*.DesktopClient/ModelManifest.xml 312 | **/*.Server/GeneratedArtifacts 313 | **/*.Server/ModelManifest.xml 314 | _Pvt_Extensions 315 | 316 | # Paket dependency manager 317 | .paket/paket.exe 318 | paket-files/ 319 | 320 | # FAKE - F# Make 321 | .fake/ 322 | 323 | # CodeRush personal settings 324 | .cr/personal 325 | 326 | # Python Tools for Visual Studio (PTVS) 327 | __pycache__/ 328 | *.pyc 329 | 330 | # Cake - Uncomment if you are using it 331 | # tools/** 332 | # !tools/packages.config 333 | 334 | # Tabs Studio 335 | *.tss 336 | 337 | # Telerik's JustMock configuration file 338 | *.jmconfig 339 | 340 | # BizTalk build output 341 | *.btp.cs 342 | *.btm.cs 343 | *.odx.cs 344 | *.xsd.cs 345 | 346 | # OpenCover UI analysis results 347 | OpenCover/ 348 | 349 | # Azure Stream Analytics local run output 350 | ASALocalRun/ 351 | 352 | # MSBuild Binary and Structured Log 353 | *.binlog 354 | 355 | # NVidia Nsight GPU debugger configuration file 356 | *.nvuser 357 | 358 | # MFractors (Xamarin productivity tool) working folder 359 | .mfractor/ 360 | 361 | # Local History for Visual Studio 362 | .localhistory/ 363 | 364 | # Visual Studio History (VSHistory) files 365 | .vshistory/ 366 | 367 | # BeatPulse healthcheck temp database 368 | healthchecksdb 369 | 370 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 371 | MigrationBackup/ 372 | 373 | # Ionide (cross platform F# VS Code tools) working folder 374 | .ionide/ 375 | 376 | # Fody - auto-generated XML schema 377 | FodyWeavers.xsd 378 | 379 | # VS Code files for those working on multiple tools 380 | .vscode/* 381 | !.vscode/settings.json 382 | !.vscode/tasks.json 383 | !.vscode/launch.json 384 | !.vscode/extensions.json 385 | *.code-workspace 386 | 387 | # Local History for Visual Studio Code 388 | .history/ 389 | 390 | # Windows Installer files from build outputs 391 | *.cab 392 | *.msi 393 | *.msix 394 | *.msm 395 | *.msp 396 | 397 | # JetBrains Rider 398 | *.sln.iml -------------------------------------------------------------------------------- /AFL/test_ioctl/header.h: -------------------------------------------------------------------------------- 1 | #ifndef __MAIN_H__ 2 | #define __MAIN_H__ 3 | 4 | #define _CRT_SECURE_NO_WARNINGS 5 | #include // winsock2 MUST include before windows 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "helper.h" 13 | 14 | #define PFATAL_GLE(msg) { _tprintf(_T("%s, GLE=%d\n"), msg, GetLastError()); exit(1); } 15 | #define PFATAL(msg) { _tprintf(_T("%s\n"), msg); exit(1); } 16 | 17 | void INIT(); 18 | void PRE(); 19 | void POST(); 20 | void FINI(); 21 | extern DWORD PERSISTENT_COUNT; 22 | extern HANDLE hHelper; 23 | ULONG_PTR HELPER_GetModuleSectionAddress(PTCHAR pBuffer); 24 | DWORD HELPER_ReadMemory(PBYTE dwAddr, PBYTE pRetBuffer, DWORD dwLen); 25 | DWORD HELPER_WriteMemory(PBYTE dwAddr, PBYTE pRetBuffer, DWORD dwLen); 26 | ULONG_PTR HELPER_AllocateMemory(DWORD dwPoolType, DWORD dwTag, DWORD dwLen); 27 | DWORD HELPER_FreeMemory(PBYTE dwAddr, DWORD dwTag); 28 | DWORD HELPER_MapMemory(ULONG_PTR dwAddr, DWORD dwLen, ULONG_PTR* ptrUserAddr, ULONG_PTR* ptrMdl); 29 | DWORD HELPER_UnmapMemory(ULONG_PTR ptrUserAddr, ULONG_PTR ptrMdl); 30 | DWORD HELPER_ResetBuffer(); 31 | 32 | static void LOG(TCHAR *fmt, ...) 33 | { 34 | TCHAR szBuf[MAX_PATH] = { 0 }; 35 | 36 | va_list ap; 37 | va_start(ap, fmt); 38 | _vstprintf_s(szBuf, fmt, ap); 39 | va_end(ap); 40 | OutputDebugString(szBuf); 41 | 42 | //_tprintf(L"%s", szBuf); 43 | } 44 | 45 | static void hexDump(char *desc, void *addr, int len) { 46 | int i; 47 | unsigned char buff[17]; 48 | unsigned char *pc = (unsigned char*)addr; 49 | 50 | // Output description if given. 51 | if (desc != NULL) 52 | printf("%s:\n", desc); 53 | 54 | if (len == 0) { 55 | printf(" ZERO LENGTH\n"); 56 | return; 57 | } 58 | if (len < 0) { 59 | printf(" NEGATIVE LENGTH: %i\n", len); 60 | return; 61 | } 62 | 63 | // Process every byte in the data. 64 | for (i = 0; i < len; i++) { 65 | // Multiple of 16 means new line (with line offset). 66 | 67 | if ((i % 16) == 0) { 68 | // Just don't print ASCII for the zeroth line. 69 | if (i != 0) 70 | printf(" %s\n", buff); 71 | 72 | // Output the offset. 73 | printf(" %04x ", i); 74 | } 75 | 76 | // Now the hex code for the specific character. 77 | printf(" %02x", pc[i]); 78 | 79 | // And store a printable ASCII character for later. 80 | if ((pc[i] < 0x20) || (pc[i] > 0x7e)) 81 | buff[i % 16] = '.'; 82 | else 83 | buff[i % 16] = pc[i]; 84 | buff[(i % 16) + 1] = '\0'; 85 | } 86 | 87 | // Pad out last line if not exactly 16 characters. 88 | while ((i % 16) != 0) { 89 | printf(" "); 90 | i++; 91 | } 92 | 93 | // And print the final ASCII bit. 94 | printf(" %s\n", buff); 95 | } 96 | 97 | #endif 98 | -------------------------------------------------------------------------------- /AFL/test_ioctl/helper.h: -------------------------------------------------------------------------------- 1 | #ifndef __HELPER_DRIVER_H__ 2 | #define __HELPER_DRIVER_H__ 3 | 4 | #define IOCTL_HELPER_GET_SECTION_ADDRESS CTL_CODE(FILE_DEVICE_UNKNOWN, 801, METHOD_BUFFERED, FILE_ANY_ACCESS) 5 | #define IOCTL_HELPER_READ_MEMORY CTL_CODE(FILE_DEVICE_UNKNOWN, 802, METHOD_BUFFERED, FILE_ANY_ACCESS) 6 | #define IOCTL_HELPER_WRITE_MEMORY CTL_CODE(FILE_DEVICE_UNKNOWN, 803, METHOD_BUFFERED, FILE_ANY_ACCESS) 7 | #define IOCTL_HELPER_ALLOCATE_MEMORY CTL_CODE(FILE_DEVICE_UNKNOWN, 804, METHOD_BUFFERED, FILE_ANY_ACCESS) 8 | #define IOCTL_HELPER_FREE_MEMORY CTL_CODE(FILE_DEVICE_UNKNOWN, 805, METHOD_BUFFERED, FILE_ANY_ACCESS) 9 | #define IOCTL_HELPER_MAP_MEMORY CTL_CODE(FILE_DEVICE_UNKNOWN, 806, METHOD_BUFFERED, FILE_ANY_ACCESS) 10 | #define IOCTL_HELPER_UNMAP_MEMORY CTL_CODE(FILE_DEVICE_UNKNOWN, 807, METHOD_BUFFERED, FILE_ANY_ACCESS) 11 | #define IOCTL_HELPER_DUMP_AND_RESET_CALLBACK CTL_CODE(FILE_DEVICE_UNKNOWN, 808, METHOD_BUFFERED, FILE_ANY_ACCESS) 12 | 13 | #endif 14 | -------------------------------------------------------------------------------- /AFL/test_ioctl/interface.cpp: -------------------------------------------------------------------------------- 1 | #include "header.h" 2 | 3 | ULONG_PTR HELPER_GetModuleSectionAddress(PTCHAR pBuffer) 4 | { 5 | DWORD dwRet = 0, dwOutLen = 0, dwInLen = 0; 6 | ULONG_PTR dwData = 0; 7 | BOOLEAN bRet = 0; 8 | 9 | dwInLen = (DWORD)_tcsclen(pBuffer) + 1; 10 | dwInLen *= 2; 11 | dwOutLen = sizeof(ULONG_PTR); 12 | bRet = DeviceIoControl(hHelper, IOCTL_HELPER_GET_SECTION_ADDRESS, pBuffer, dwInLen, &dwData, dwOutLen, &dwRet, NULL); 13 | if (!bRet) { 14 | LOG(_T("HELPER_GetModuleSectionAddress(%s) failed, GLE(%x)\n"), pBuffer, GetLastError()); 15 | } 16 | return dwData; 17 | } 18 | 19 | DWORD HELPER_ReadMemory(PBYTE dwAddr, PBYTE pRetBuffer, DWORD dwLen) 20 | { 21 | DWORD dwRet = 0; 22 | BOOLEAN bRet = 0; 23 | 24 | bRet = DeviceIoControl(hHelper, IOCTL_HELPER_READ_MEMORY, &dwAddr, dwLen, pRetBuffer, dwLen, &dwRet, NULL); 25 | if (!bRet) { 26 | LOG(_T("HELPER_ReadMemory(%p, %p, %x) failed, GLE(%x)\n"), dwAddr, pRetBuffer, dwLen, GetLastError()); 27 | } 28 | return dwRet; 29 | } 30 | 31 | DWORD HELPER_WriteMemory(PBYTE dwAddr, PBYTE pRetBuffer, DWORD dwLen) 32 | { 33 | DWORD dwRet = 0; 34 | BOOLEAN bRet = 0; 35 | 36 | bRet = DeviceIoControl(hHelper, IOCTL_HELPER_WRITE_MEMORY, &dwAddr, dwLen, pRetBuffer, dwLen, &dwRet, NULL); 37 | if (!bRet) { 38 | LOG(_T("HELPER_WriteMemory(%p, %p, %x) failed, GLE(%x)\n"), dwAddr, pRetBuffer, dwLen, GetLastError()); 39 | } 40 | return dwRet; 41 | } 42 | 43 | ULONG_PTR HELPER_AllocateMemory(DWORD dwPoolType, DWORD dwLen, DWORD dwTag) 44 | { 45 | DWORD dwRet = 0; 46 | BOOLEAN bRet = 0; 47 | ULONG_PTR dwData = 0; 48 | 49 | DWORD in[] = { dwPoolType, dwLen, dwTag }; 50 | bRet = DeviceIoControl(hHelper, IOCTL_HELPER_ALLOCATE_MEMORY, in, sizeof(in), &dwData, sizeof(&dwData), &dwRet, NULL); 51 | if (!bRet) { 52 | LOG(_T("HELPER_AllocateMemory(%x, %x, 0x%x) failed, GLE(%x)\n"), dwPoolType, dwLen, dwTag, GetLastError()); 53 | } 54 | return dwData; 55 | } 56 | 57 | DWORD HELPER_FreeMemory(PBYTE dwAddr, DWORD dwTag) 58 | { 59 | DWORD dwRet = 0; 60 | BOOLEAN bRet = 0; 61 | 62 | // DWORD in[] = { (ULONG_PTR)dwAddr, dwTag }; 63 | unsigned char in2[sizeof(ULONG_PTR) + sizeof(DWORD)]{}; 64 | *(ULONG_PTR*)in2 = (ULONG_PTR)dwAddr; 65 | *(DWORD*)((ULONG_PTR)in2 + sizeof(ULONG_PTR)) = dwTag; 66 | //bRet = DeviceIoControl(hHelper, IOCTL_HELPER_FREE_MEMORY, in, sizeof(in), NULL, NULL, &dwRet, NULL); 67 | bRet = DeviceIoControl(hHelper, IOCTL_HELPER_FREE_MEMORY, in2, sizeof(in2), NULL, NULL, &dwRet, NULL); 68 | if (!bRet) { 69 | LOG(_T("HELPER_FreeMemory(%p, %x) failed, GLE(%x)\n"), dwAddr, dwTag, GetLastError()); 70 | } 71 | return dwRet; 72 | } 73 | 74 | DWORD HELPER_MapMemory(ULONG_PTR dwAddr, DWORD dwLen, ULONG_PTR*ptrUserAddr, ULONG_PTR *ptrMdl) 75 | { 76 | DWORD dwRet = 0; 77 | BOOLEAN bRet = 0; 78 | 79 | //DWORD in[] = { (ULONG_PTR)dwAddr, dwLen }; 80 | unsigned char in2[sizeof(ULONG_PTR) + sizeof(DWORD)]{}; 81 | *(ULONG_PTR*)in2 = (ULONG_PTR)dwAddr; 82 | *(DWORD*)((ULONG_PTR)in2 + sizeof(ULONG_PTR)) = dwLen; 83 | ULONG_PTR out[2] = { 0 }; 84 | bRet = DeviceIoControl(hHelper, IOCTL_HELPER_MAP_MEMORY, in2, sizeof(in2), out, sizeof(out), &dwRet, NULL); 85 | if (!bRet) { 86 | LOG(_T("HELPER_MapMemory(%p, %x, %p, %p) failed, GLE(%x)\n"), dwAddr, dwLen, ptrUserAddr, ptrMdl); 87 | } 88 | *ptrUserAddr = out[0]; 89 | *ptrMdl = out[1]; 90 | return dwRet; 91 | } 92 | 93 | DWORD HELPER_UnmapMemory(ULONG_PTR ptrUserAddr, ULONG_PTR ptrMdl) 94 | { 95 | DWORD dwRet = 0; 96 | BOOLEAN bRet = 0; 97 | 98 | ULONG_PTR in[] = { (ULONG_PTR)ptrUserAddr, (ULONG_PTR)ptrMdl }; 99 | bRet = DeviceIoControl(hHelper, IOCTL_HELPER_UNMAP_MEMORY, in, sizeof(in), NULL, NULL, &dwRet, NULL); 100 | if (!bRet) { 101 | LOG(_T("HELPER_UnmapMemory(%p, %p) failed, GLE(%x)\n"), ptrUserAddr, ptrMdl); 102 | } 103 | return dwRet; 104 | } 105 | 106 | DWORD HELPER_ResetBuffer() 107 | { 108 | DWORD dwRet = 0; 109 | BOOLEAN bRet = 0; 110 | 111 | bRet = DeviceIoControl(hHelper, IOCTL_HELPER_DUMP_AND_RESET_CALLBACK, NULL, NULL, NULL, NULL, &dwRet, NULL); 112 | if (!bRet) { 113 | LOG(_T("HELPER_ResetBuffer() failed, GLE(%x)\n"), GetLastError()); 114 | } 115 | return dwRet; 116 | } -------------------------------------------------------------------------------- /AFL/test_ioctl/main.cpp: -------------------------------------------------------------------------------- 1 | #include "header.h" 2 | 3 | #define _tprintf (0); 4 | 5 | DWORD g_szFile; 6 | HANDLE g_hDev; 7 | 8 | struct _LSA_UNICODE_STRING { 9 | USHORT Length; 10 | USHORT MaximumLength; 11 | const wchar_t* Buffer; 12 | }; 13 | 14 | struct _OBJECT_ATTRIBUTES { 15 | ULONG Length; 16 | HANDLE RootDirectory; 17 | _LSA_UNICODE_STRING* ObjectName; 18 | ULONG Attributes; 19 | PVOID SecurityDescriptor; 20 | PVOID SecurityQualityOfService; 21 | }; 22 | 23 | BOOL analyze_potential_leaks(PVOID buffer, UINT size) { 24 | BOOL result = FALSE; 25 | UINT i; 26 | if (size < 8) { 27 | return FALSE; 28 | } 29 | 30 | for (i = 0; i < size; i += 8) { 31 | if (i > size) 32 | break; 33 | DWORD64 content = ((DWORD64 *)buffer)[i]; 34 | if ((content >= 0xFFFF800000000000 && content <= 0xFFFFFFFFFFFFFFFF) && content != 0xFFFFFFFFFFFFFFFF) { 35 | printf("LEAK? %i: %p\n", i, content); 36 | } 37 | result = TRUE; 38 | } 39 | 40 | return result; 41 | } 42 | 43 | PVOID mapInputFile(TCHAR* filepath) 44 | { 45 | HANDLE hFile, hMap; 46 | PVOID pView = NULL; 47 | 48 | g_szFile = 0; 49 | 50 | // Open file 51 | hFile = CreateFile(filepath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); 52 | if (hFile != INVALID_HANDLE_VALUE) { 53 | 54 | // Create the file mapping object 55 | hMap = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL); 56 | if (hMap) { 57 | pView = MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0); 58 | if (pView) { 59 | _tprintf(_T("Successfully mapped view of file %s as input\n"), filepath); 60 | 61 | g_szFile = GetFileSize(hFile, NULL); 62 | if (g_szFile == INVALID_FILE_SIZE) { 63 | _tprintf(_T("Failed to get file size with error %x\n"), GetLastError()); 64 | } 65 | } 66 | else { 67 | _tprintf(_T("Failed to map view of file %s with error %x\n"), filepath, GetLastError()); 68 | } 69 | CloseHandle(hMap); 70 | } 71 | else { 72 | _tprintf(_T("Failed to create file mapping for %s with error %x\n"), filepath, GetLastError()); 73 | } 74 | CloseHandle(hFile); 75 | } 76 | else { 77 | _tprintf(_T("Failed to open file %s with error %#.8x\n"), filepath, GetLastError()); 78 | } 79 | return pView; 80 | } 81 | HANDLE openDev(LPCTSTR deviceName) { 82 | HANDLE hDev; 83 | 84 | if (!_tcsncmp(deviceName, _T("\\\\.\\"), 4)) { 85 | hDev = CreateFile(deviceName, MAXIMUM_ALLOWED, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); 86 | if (hDev == INVALID_HANDLE_VALUE) { 87 | _tprintf(_T("Device file %s open failed with GetLastError %#x\n"), deviceName, GetLastError()); 88 | exit(EOF); 89 | } 90 | } 91 | else { 92 | 93 | NTSTATUS 94 | (NTAPI 95 | *ntCreateFile)( 96 | _Out_ PHANDLE FileHandle, 97 | _In_ ACCESS_MASK DesiredAccess, 98 | _In_ _OBJECT_ATTRIBUTES* ObjectAttributes, 99 | _Out_ PVOID IoStatusBlock, 100 | _In_opt_ PLARGE_INTEGER AllocationSize, 101 | _In_ ULONG FileAttributes, 102 | _In_ ULONG ShareAccess, 103 | _In_ ULONG CreateDisposition, 104 | _In_ ULONG CreateOptions, 105 | _In_ PVOID EaBuffer, 106 | _In_ ULONG EaLength 107 | ); 108 | 109 | HMODULE ntdll = GetModuleHandleA("ntdll"); 110 | *(void**)&ntCreateFile = GetProcAddress(ntdll, "NtCreateFile"); 111 | 112 | _LSA_UNICODE_STRING name; 113 | name.Buffer = deviceName; 114 | name.Length = name.MaximumLength = _tcslen(deviceName) * 2; 115 | 116 | _OBJECT_ATTRIBUTES attr; 117 | attr.Length = sizeof(attr); 118 | attr.RootDirectory = 0; 119 | attr.ObjectName = &name; 120 | attr.Attributes = 64; 121 | attr.SecurityDescriptor = 0; 122 | attr.SecurityQualityOfService = 0; 123 | 124 | UINT64 tmp[10] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; 125 | 126 | NTSTATUS ns = ntCreateFile(&hDev, MAXIMUM_ALLOWED, &attr, tmp, 0, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, OPEN_EXISTING, 0, 0, 0); 127 | if (ns) { 128 | _tprintf(_T("Device file %s open failed with NTSTATUS %#x\n"), deviceName, ns); 129 | exit(EOF); 130 | } 131 | } 132 | 133 | _tprintf(_T("Device file %s opened successfully\n"), deviceName); 134 | return hDev; 135 | } 136 | 137 | DWORD sendIoctl(HANDLE hDev, DWORD iocode, PVOID inbuf, DWORD inlen, PVOID outbuf, DWORD outlen) 138 | { 139 | BOOL bResult, bSent = FALSE, bAddress = FALSE; 140 | LPTSTR errormessage; 141 | DWORD bytesreturned = 0; 142 | DWORD error; 143 | 144 | if (!iocode) return 0; 145 | 146 | if (inbuf) { 147 | _tprintf(_T("Sending ioctl %#.8x\n"), iocode); 148 | bResult = DeviceIoControl(hDev, iocode, inbuf, inlen, outbuf, outlen, &bytesreturned, NULL); 149 | error = bResult ? ERROR_SUCCESS : GetLastError(); 150 | bSent = TRUE; 151 | } 152 | 153 | if (bSent) { 154 | if (error == ERROR_SUCCESS) { 155 | _tprintf(_T("IOCTL completed SUCCESSFULLY, returned %u bytes\n"), bytesreturned); 156 | } 157 | else { 158 | // Verbose error 159 | FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER, 0, error, 0, (LPTSTR)&errormessage, 4, NULL); 160 | _tprintf(_T("IOCTL FAILED with error %x: %s\n"), error, errormessage); 161 | LocalFree(errormessage); 162 | } 163 | } 164 | 165 | if (bSent) { 166 | //hexDump(NULL, outbuf, bytesreturned); 167 | //analyze_potential_leaks(outbuf, bytesreturned); 168 | } 169 | 170 | return bytesreturned; 171 | } 172 | 173 | void init_device(TCHAR* name) { 174 | g_hDev = openDev(name); 175 | } 176 | 177 | 178 | PBYTE readFromFile(PTCHAR input_path) 179 | { 180 | HANDLE hFile = CreateFile(input_path, // file to open 181 | GENERIC_READ, // open for reading 182 | FILE_SHARE_READ, // share for reading 183 | NULL, // default security 184 | OPEN_EXISTING, // existing file only 185 | FILE_ATTRIBUTE_NORMAL, // normal file 186 | NULL); // no attr. template 187 | if (hFile == INVALID_HANDLE_VALUE) { 188 | _tprintf(_T("Failed opening input file: %d\n"), GetLastError()); 189 | return NULL; 190 | } 191 | PBYTE buf = (PBYTE)malloc(0x1000); 192 | DWORD bufSize = 0; 193 | if (!ReadFile(hFile, buf, 0x1000, &bufSize, NULL)) { 194 | _tprintf(_T("[-] Failed to read from input file: %d\n"), GetLastError()); 195 | return NULL; 196 | } 197 | g_szFile = bufSize; 198 | return buf; 199 | } 200 | 201 | void process(HANDLE hDev, DWORD iocode, PTCHAR input_path, char * buffer) { 202 | TCHAR outbuf[0x1000]{}; 203 | if (g_szFile) { 204 | sendIoctl(hDev, iocode, PVOID(buffer), g_szFile, outbuf, g_szFile); 205 | } 206 | } 207 | 208 | 209 | 210 | 211 | 212 | INT _tmain(INT argc, TCHAR* argv[]) { 213 | 214 | #ifndef UNICODE 215 | _tprintf(_T("Have to compile with unicode\n")); 216 | return 0; 217 | #endif 218 | 219 | INIT(); 220 | atexit(FINI); 221 | g_hDev = CreateFileA((LPCSTR)"\\\\.\\nal", 222 | GENERIC_READ | GENERIC_WRITE, 223 | FILE_SHARE_READ | FILE_SHARE_WRITE, 224 | NULL, 225 | OPEN_EXISTING, 226 | FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, 227 | NULL 228 | ); 229 | 230 | if (g_hDev == INVALID_HANDLE_VALUE) { 231 | printf("[-] KAFL test: Cannot get device handle: 0x%X\n", GetLastError()); 232 | ExitProcess(0); 233 | } 234 | char buffer[0x1000] = { 0 }; 235 | _setmode(_fileno(stdin), _O_BINARY); 236 | _setmode(_fileno(stdout), _O_BINARY); 237 | 238 | while (PERSISTENT_COUNT--) { 239 | PRE(); 240 | g_szFile = fread(buffer, 1, sizeof(buffer), stdin); 241 | process(g_hDev, 0xDC7FE408, argv[1], buffer); 242 | memset(buffer, 0, 0x1000); 243 | POST(); 244 | } 245 | 246 | } 247 | -------------------------------------------------------------------------------- /AFL/test_ioctl/persistence.cpp: -------------------------------------------------------------------------------- 1 | #include "header.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #pragma comment(lib,"ntdll.lib") 8 | 9 | #define SHM_ENV_VAR _T("__AFL_SHM_ID") 10 | #define PIPE_ENV_VAR _T("__AFL_PIPE_ID") 11 | #define MAP_SIZE 0x10000 12 | #define PAGE_SIZE 0x1000 13 | //#define MEM_BARRIER() _ReadWriteBarrier(); MemoryBarrier() 14 | #define MEM_BARRIER() MemoryBarrier() 15 | #define DRIVER_NAME _T("\\\\.\\helper") 16 | 17 | ULONG_PTR COV_ADDR = 0; 18 | ULONG_PTR KCOV_ADDR = 0; 19 | ULONG_PTR MDL_ADDR = 0; 20 | ULONG_PTR shared_mem; 21 | HANDLE pipe; 22 | DWORD PERSISTENT_COUNT; 23 | PTCHAR target; 24 | HANDLE hHelper; 25 | DWORD PID; 26 | DWORD CALLBACK_ADDR = 0; 27 | 28 | typedef struct _RTL_PROCESS_MODULE_INFORMATION 29 | { 30 | HANDLE Section; 31 | PVOID MappedBase; 32 | PVOID ImageBase; 33 | ULONG ImageSize; 34 | ULONG Flags; 35 | USHORT LoadOrderIndex; 36 | USHORT InitOrderIndex; 37 | USHORT LoadCount; 38 | USHORT OffsetToFileName; 39 | UCHAR FullPathName[256]; 40 | } RTL_PROCESS_MODULE_INFORMATION, *PRTL_PROCESS_MODULE_INFORMATION; 41 | 42 | typedef struct _RTL_PROCESS_MODULES 43 | { 44 | ULONG NumberOfModules; 45 | RTL_PROCESS_MODULE_INFORMATION Modules[1]; 46 | } RTL_PROCESS_MODULES, *PRTL_PROCESS_MODULES; 47 | 48 | typedef enum _SYSTEM_INFORMATION_CLASS 49 | { 50 | SystemBasicInformation = 0, 51 | SystemProcessorInformation = 1, // obsolete...delete 52 | SystemPerformanceInformation = 2, 53 | SystemTimeOfDayInformation = 3, 54 | SystemPathInformation = 4, 55 | SystemProcessInformation = 5, 56 | SystemCallCountInformation = 6, 57 | SystemDeviceInformation = 7, 58 | SystemProcessorPerformanceInformation = 8, 59 | SystemFlagsInformation = 9, 60 | SystemCallTimeInformation = 10, 61 | SystemModuleInformation = 11, 62 | } SYSTEM_INFORMATION_CLASS; 63 | 64 | #define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0) 65 | 66 | typedef struct _UNICODE_STRING { 67 | USHORT Length; 68 | USHORT MaximumLength; 69 | PWSTR Buffer; 70 | } UNICODE_STRING, *PUNICODE_STRING; 71 | 72 | typedef struct _SYSTEM_PROCESS_INFO 73 | { 74 | ULONG NextEntryOffset; 75 | ULONG NumberOfThreads; 76 | LARGE_INTEGER Reserved[3]; 77 | LARGE_INTEGER CreateTime; 78 | LARGE_INTEGER UserTime; 79 | LARGE_INTEGER KernelTime; 80 | UNICODE_STRING ImageName; 81 | ULONG BasePriority; 82 | HANDLE ProcessId; 83 | HANDLE InheritedFromProcessId; 84 | }SYSTEM_PROCESS_INFO, *PSYSTEM_PROCESS_INFO; 85 | 86 | typedef NTSTATUS(NTAPI * _NtQuerySystemInformation)( 87 | SYSTEM_INFORMATION_CLASS, 88 | PVOID, 89 | ULONG, 90 | PULONG); 91 | 92 | SIZE_T get_driver_address(LPCSTR driver, LPCSTR symbol) { 93 | NTSTATUS status; 94 | ULONG i; 95 | ULONG len; 96 | 97 | PRTL_PROCESS_MODULES ModuleInfo; 98 | 99 | _NtQuerySystemInformation NtQuerySystemInformation = (_NtQuerySystemInformation)GetProcAddress(GetModuleHandle(L"ntdll.dll"), "NtQuerySystemInformation"); 100 | NtQuerySystemInformation((SYSTEM_INFORMATION_CLASS)SystemModuleInformation, NULL, 0, &len); 101 | 102 | ModuleInfo = (PRTL_PROCESS_MODULES)VirtualAlloc(NULL, len, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); 103 | if (!ModuleInfo) 104 | { 105 | printf("\nUnable to allocate memory for module list (%d)\n", GetLastError()); 106 | return NULL; 107 | } 108 | 109 | if (!NT_SUCCESS(status = NtQuerySystemInformation((SYSTEM_INFORMATION_CLASS)SystemModuleInformation, ModuleInfo, len, &len))) 110 | { 111 | printf("\nError: Unable to query module list (%#x)\n", status); 112 | VirtualFree(ModuleInfo, 0, MEM_RELEASE); 113 | return NULL; 114 | } 115 | 116 | SIZE_T addr = 0; 117 | for (i = 0; iNumberOfModules; i++) 118 | { 119 | if (!_stricmp((CHAR*)ModuleInfo->Modules[i].FullPathName + ModuleInfo->Modules[i].OffsetToFileName, driver)) { 120 | //printf("\nImage base: %#x\n", ModuleInfo->Modules[i].ImageBase); 121 | //printf("\nImage full path: %s\n", ModuleInfo->Modules[i].FullPathName); 122 | 123 | // DONT_RESOLVE_DLL_REFERENCES on some driver return ERROR_MOD_NOT_FOUND 124 | HINSTANCE h = LoadLibraryExA((LPCSTR)ModuleInfo->Modules[i].FullPathName + 4, NULL, DONT_RESOLVE_DLL_REFERENCES); 125 | if (symbol) { 126 | addr = (SIZE_T)ModuleInfo->Modules[i].ImageBase + (SIZE_T)GetProcAddress(h, symbol) - (SIZE_T)h; 127 | } 128 | else { 129 | addr = (SIZE_T)ModuleInfo->Modules[i].ImageBase; 130 | } 131 | break; 132 | } 133 | } 134 | 135 | VirtualFree(ModuleInfo, 0, MEM_RELEASE); 136 | 137 | return addr; 138 | } 139 | 140 | SIZE_T get_helper_value(LPCSTR symbol) { 141 | SIZE_T v = get_driver_address("helper.sys", symbol); 142 | SIZE_T v2 = 0; 143 | if (!HELPER_ReadMemory((PBYTE)v, (PBYTE)&v2, sizeof(DWORD))) { 144 | PFATAL(_T("Can't read memory\n")); 145 | } 146 | return v2; 147 | } 148 | 149 | void setup_shmem() { 150 | HANDLE map_file; 151 | 152 | map_file = OpenFileMapping( 153 | FILE_MAP_ALL_ACCESS, // read/write access 154 | FALSE, // do not inherit the name 155 | _tgetenv(SHM_ENV_VAR)); // name of mapping object 156 | 157 | if (map_file == NULL) PFATAL(_T("Error accesing shared memory")); 158 | 159 | shared_mem = (ULONG_PTR)MapViewOfFile(map_file, // handle to map object 160 | FILE_MAP_ALL_ACCESS, // read/write permission 161 | 0, 162 | 0, 163 | MAP_SIZE); 164 | 165 | if (shared_mem == NULL) PFATAL(_T("Error accesing shared memory")); 166 | LOG(_T("shared_mem @ %p\n"), shared_mem); 167 | } 168 | 169 | void setup_pipe() { 170 | pipe = CreateFile( 171 | _tgetenv(PIPE_ENV_VAR), // pipe name 172 | GENERIC_READ | // read and write access 173 | GENERIC_WRITE, 174 | 0, // no sharing 175 | NULL, // default security attributes 176 | OPEN_EXISTING, // opens existing pipe 177 | 0, // default attributes 178 | NULL); // no template file 179 | 180 | if (pipe == INVALID_HANDLE_VALUE) PFATAL(_T("Error connecting to pipe")); 181 | } 182 | 183 | void PRE() { 184 | 185 | if (!_tgetenv(SHM_ENV_VAR)) return; 186 | 187 | CHAR command = 0; 188 | DWORD num_read; 189 | 190 | ReadFile(pipe, &command, 1, &num_read, NULL); // blocking 191 | if (command != 'R') { 192 | PFATAL(_T("Wrong command @ PRE()")); 193 | } 194 | 195 | BYTE buf[MAP_SIZE + PAGE_SIZE] = { 0 }; 196 | memcpy(buf + MAP_SIZE + 0x10, &PID, sizeof(DWORD)); 197 | 198 | if (CALLBACK_ADDR) { 199 | memcpy(buf + MAP_SIZE + 0x20, &CALLBACK_ADDR, sizeof(DWORD)); 200 | } 201 | if (_tgetenv(_T("DUMP_TRACE"))) HELPER_ResetBuffer(); 202 | 203 | memcpy((void*)COV_ADDR, buf, sizeof(buf)); 204 | } 205 | 206 | void dump_trace_buffer() { 207 | DWORD tbl_idx = get_helper_value("tbl_idx"); 208 | printf("number of basic block = %d\n", tbl_idx); 209 | if (!tbl_idx) return; 210 | 211 | PBYTE base = (PBYTE)get_driver_address(getenv("AFL_TARGET"), NULL); 212 | PBYTE tbl = (PBYTE)get_helper_value("tbl"); 213 | PULONG p = (PULONG)malloc(tbl_idx*sizeof(PULONG)); 214 | if (!HELPER_ReadMemory(tbl, (PBYTE)p, tbl_idx*sizeof(PULONG))) { 215 | PFATAL(_T("Can't read memory\n")); 216 | } 217 | 218 | // convert the address with mapping.txt 219 | PCHAR out_file = "trace.txt"; 220 | _unlink(out_file); 221 | DWORD fd = _open(out_file, O_WRONLY | O_CREAT | O_EXCL, 0600); 222 | if (fd < 0) PFATAL(_T("Unable to create trace file")); 223 | 224 | FILE* f = _fdopen(fd, "w"); 225 | for (DWORD i = 0; i < tbl_idx; i++) { 226 | fprintf(f, "%x\n", (PBYTE)p[i] - (PBYTE)base); 227 | } 228 | fclose(f); 229 | 230 | printf("trace.txt is created\n"); 231 | } 232 | 233 | void POST() { 234 | 235 | if (!_tgetenv(SHM_ENV_VAR)) return; 236 | 237 | memcpy((void *)shared_mem, (void *)COV_ADDR, MAP_SIZE); 238 | MEM_BARRIER(); 239 | 240 | if (_tgetenv(_T("DUMP_TRACE"))) dump_trace_buffer(); 241 | 242 | DWORD num_written; 243 | if (PERSISTENT_COUNT) { 244 | WriteFile(pipe, "K", 1, &num_written, NULL); 245 | } 246 | else { 247 | WriteFile(pipe, "Q", 1, &num_written, NULL); 248 | } 249 | 250 | } 251 | 252 | void CRASH() { 253 | // PASS 254 | } 255 | 256 | void INIT() { 257 | 258 | target = _tgetenv(_T("AFL_TARGET")); 259 | if (!target) { 260 | PFATAL(_T("Can not getenv AFL_TARGET\n")); 261 | } 262 | 263 | if (_tgetenv(SHM_ENV_VAR)) { 264 | setup_shmem(); 265 | setup_pipe(); 266 | PERSISTENT_COUNT = 10000; 267 | 268 | hHelper = CreateFile(DRIVER_NAME, MAXIMUM_ALLOWED, 0, NULL, OPEN_EXISTING, 0, NULL); 269 | if (hHelper == INVALID_HANDLE_VALUE) { 270 | PFATAL(_T("Can't create helper driver\n")); 271 | } 272 | 273 | LOG(_T("GetModuleSectionAddress(%s)\n"), target); 274 | KCOV_ADDR = (ULONG_PTR)HELPER_GetModuleSectionAddress(target); 275 | LOG(_T("Kernel coverage map @ 0x%p\n"), KCOV_ADDR); 276 | if (!KCOV_ADDR) { 277 | PFATAL(_T("Can't get section address\n")); 278 | } 279 | 280 | //CALLBACK_ADDR = get_driver_address("helper.sys", "_callback@0"); 281 | PID = _getpid(); 282 | 283 | HELPER_MapMemory(KCOV_ADDR, MAP_SIZE + PAGE_SIZE, &COV_ADDR, &MDL_ADDR); 284 | LOG(_T("User coverage map @ 0x%p\n"), COV_ADDR); 285 | LOG(_T("MDL @ 0x%p\n"), MDL_ADDR); 286 | 287 | } 288 | else { 289 | PERSISTENT_COUNT = 1; 290 | } 291 | 292 | } 293 | 294 | void FINI() 295 | { 296 | if (COV_ADDR && MDL_ADDR) { 297 | HELPER_UnmapMemory(COV_ADDR, MDL_ADDR); 298 | } 299 | } -------------------------------------------------------------------------------- /AFL/test_ioctl/test_ioctl.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.30503.244 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "test_ioctl", "test_ioctl.vcxproj", "{78704997-9607-45BD-92B3-DE5E7676FCD8}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|x64 = Debug|x64 11 | Debug|x86 = Debug|x86 12 | Release|x64 = Release|x64 13 | Release|x86 = Release|x86 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {78704997-9607-45BD-92B3-DE5E7676FCD8}.Debug|x64.ActiveCfg = Debug|x64 17 | {78704997-9607-45BD-92B3-DE5E7676FCD8}.Debug|x64.Build.0 = Debug|x64 18 | {78704997-9607-45BD-92B3-DE5E7676FCD8}.Debug|x86.ActiveCfg = Debug|Win32 19 | {78704997-9607-45BD-92B3-DE5E7676FCD8}.Debug|x86.Build.0 = Debug|Win32 20 | {78704997-9607-45BD-92B3-DE5E7676FCD8}.Release|x64.ActiveCfg = Release|x64 21 | {78704997-9607-45BD-92B3-DE5E7676FCD8}.Release|x64.Build.0 = Release|x64 22 | {78704997-9607-45BD-92B3-DE5E7676FCD8}.Release|x86.ActiveCfg = Release|Win32 23 | {78704997-9607-45BD-92B3-DE5E7676FCD8}.Release|x86.Build.0 = Release|Win32 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {EF7BE802-24CC-4928-86B6-D0DDBCEF488E} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /AFL/test_ioctl/test_ioctl.vcxproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Debug 10 | x64 11 | 12 | 13 | Release 14 | Win32 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | {78704997-9607-45BD-92B3-DE5E7676FCD8} 23 | test_ioctl 24 | 25 | 26 | 27 | Application 28 | true 29 | v142 30 | Unicode 31 | 32 | 33 | Application 34 | true 35 | v142 36 | Unicode 37 | 38 | 39 | Application 40 | false 41 | v142 42 | true 43 | Unicode 44 | 45 | 46 | Application 47 | false 48 | v142 49 | true 50 | Unicode 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | Level3 72 | Disabled 73 | true 74 | 75 | 76 | true 77 | 78 | 79 | 80 | 81 | Level3 82 | Disabled 83 | true 84 | MultiThreaded 85 | 86 | 87 | true 88 | 89 | 90 | 91 | 92 | Level3 93 | MaxSpeed 94 | true 95 | true 96 | true 97 | MultiThreaded 98 | 99 | 100 | true 101 | true 102 | true 103 | 104 | 105 | 106 | 107 | Level3 108 | MaxSpeed 109 | true 110 | true 111 | true 112 | MultiThreaded 113 | 114 | 115 | true 116 | true 117 | true 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | -------------------------------------------------------------------------------- /AFL/test_ioctl/test_ioctl.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;hm;inl;inc;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 | Header Files 20 | 21 | 22 | Header Files 23 | 24 | 25 | 26 | 27 | Source Files 28 | 29 | 30 | Source Files 31 | 32 | 33 | Source Files 34 | 35 | 36 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | GNU AFFERO GENERAL PUBLIC LICENSE 2 | Version 3, 19 November 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU Affero General Public License is a free, copyleft license for 11 | software and other kinds of works, specifically designed to ensure 12 | cooperation with the community in the case of network server software. 13 | 14 | The licenses for most software and other practical works are designed 15 | to take away your freedom to share and change the works. By contrast, 16 | our General Public Licenses are intended to guarantee your freedom to 17 | share and change all versions of a program--to make sure it remains free 18 | software for all its users. 19 | 20 | When we speak of free software, we are referring to freedom, not 21 | price. Our General Public Licenses are designed to make sure that you 22 | have the freedom to distribute copies of free software (and charge for 23 | them if you wish), that you receive source code or can get it if you 24 | want it, that you can change the software or use pieces of it in new 25 | free programs, and that you know you can do these things. 26 | 27 | Developers that use our General Public Licenses protect your rights 28 | with two steps: (1) assert copyright on the software, and (2) offer 29 | you this License which gives you legal permission to copy, distribute 30 | and/or modify the software. 31 | 32 | A secondary benefit of defending all users' freedom is that 33 | improvements made in alternate versions of the program, if they 34 | receive widespread use, become available for other developers to 35 | incorporate. Many developers of free software are heartened and 36 | encouraged by the resulting cooperation. However, in the case of 37 | software used on network servers, this result may fail to come about. 38 | The GNU General Public License permits making a modified version and 39 | letting the public access it on a server without ever releasing its 40 | source code to the public. 41 | 42 | The GNU Affero General Public License is designed specifically to 43 | ensure that, in such cases, the modified source code becomes available 44 | to the community. It requires the operator of a network server to 45 | provide the source code of the modified version running there to the 46 | users of that server. Therefore, public use of a modified version, on 47 | a publicly accessible server, gives the public access to the source 48 | code of the modified version. 49 | 50 | An older license, called the Affero General Public License and 51 | published by Affero, was designed to accomplish similar goals. This is 52 | a different license, not a version of the Affero GPL, but Affero has 53 | released a new version of the Affero GPL which permits relicensing under 54 | this license. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | TERMS AND CONDITIONS 60 | 61 | 0. Definitions. 62 | 63 | "This License" refers to version 3 of the GNU Affero General Public License. 64 | 65 | "Copyright" also means copyright-like laws that apply to other kinds of 66 | works, such as semiconductor masks. 67 | 68 | "The Program" refers to any copyrightable work licensed under this 69 | License. Each licensee is addressed as "you". "Licensees" and 70 | "recipients" may be individuals or organizations. 71 | 72 | To "modify" a work means to copy from or adapt all or part of the work 73 | in a fashion requiring copyright permission, other than the making of an 74 | exact copy. The resulting work is called a "modified version" of the 75 | earlier work or a work "based on" the earlier work. 76 | 77 | A "covered work" means either the unmodified Program or a work based 78 | on the Program. 79 | 80 | To "propagate" a work means to do anything with it that, without 81 | permission, would make you directly or secondarily liable for 82 | infringement under applicable copyright law, except executing it on a 83 | computer or modifying a private copy. Propagation includes copying, 84 | distribution (with or without modification), making available to the 85 | public, and in some countries other activities as well. 86 | 87 | To "convey" a work means any kind of propagation that enables other 88 | parties to make or receive copies. Mere interaction with a user through 89 | a computer network, with no transfer of a copy, is not conveying. 90 | 91 | An interactive user interface displays "Appropriate Legal Notices" 92 | to the extent that it includes a convenient and prominently visible 93 | feature that (1) displays an appropriate copyright notice, and (2) 94 | tells the user that there is no warranty for the work (except to the 95 | extent that warranties are provided), that licensees may convey the 96 | work under this License, and how to view a copy of this License. If 97 | the interface presents a list of user commands or options, such as a 98 | menu, a prominent item in the list meets this criterion. 99 | 100 | 1. Source Code. 101 | 102 | The "source code" for a work means the preferred form of the work 103 | for making modifications to it. "Object code" means any non-source 104 | form of a work. 105 | 106 | A "Standard Interface" means an interface that either is an official 107 | standard defined by a recognized standards body, or, in the case of 108 | interfaces specified for a particular programming language, one that 109 | is widely used among developers working in that language. 110 | 111 | The "System Libraries" of an executable work include anything, other 112 | than the work as a whole, that (a) is included in the normal form of 113 | packaging a Major Component, but which is not part of that Major 114 | Component, and (b) serves only to enable use of the work with that 115 | Major Component, or to implement a Standard Interface for which an 116 | implementation is available to the public in source code form. A 117 | "Major Component", in this context, means a major essential component 118 | (kernel, window system, and so on) of the specific operating system 119 | (if any) on which the executable work runs, or a compiler used to 120 | produce the work, or an object code interpreter used to run it. 121 | 122 | The "Corresponding Source" for a work in object code form means all 123 | the source code needed to generate, install, and (for an executable 124 | work) run the object code and to modify the work, including scripts to 125 | control those activities. However, it does not include the work's 126 | System Libraries, or general-purpose tools or generally available free 127 | programs which are used unmodified in performing those activities but 128 | which are not part of the work. For example, Corresponding Source 129 | includes interface definition files associated with source files for 130 | the work, and the source code for shared libraries and dynamically 131 | linked subprograms that the work is specifically designed to require, 132 | such as by intimate data communication or control flow between those 133 | subprograms and other parts of the work. 134 | 135 | The Corresponding Source need not include anything that users 136 | can regenerate automatically from other parts of the Corresponding 137 | Source. 138 | 139 | The Corresponding Source for a work in source code form is that 140 | same work. 141 | 142 | 2. Basic Permissions. 143 | 144 | All rights granted under this License are granted for the term of 145 | copyright on the Program, and are irrevocable provided the stated 146 | conditions are met. This License explicitly affirms your unlimited 147 | permission to run the unmodified Program. The output from running a 148 | covered work is covered by this License only if the output, given its 149 | content, constitutes a covered work. This License acknowledges your 150 | rights of fair use or other equivalent, as provided by copyright law. 151 | 152 | You may make, run and propagate covered works that you do not 153 | convey, without conditions so long as your license otherwise remains 154 | in force. You may convey covered works to others for the sole purpose 155 | of having them make modifications exclusively for you, or provide you 156 | with facilities for running those works, provided that you comply with 157 | the terms of this License in conveying all material for which you do 158 | not control copyright. Those thus making or running the covered works 159 | for you must do so exclusively on your behalf, under your direction 160 | and control, on terms that prohibit them from making any copies of 161 | your copyrighted material outside their relationship with you. 162 | 163 | Conveying under any other circumstances is permitted solely under 164 | the conditions stated below. Sublicensing is not allowed; section 10 165 | makes it unnecessary. 166 | 167 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 168 | 169 | No covered work shall be deemed part of an effective technological 170 | measure under any applicable law fulfilling obligations under article 171 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 172 | similar laws prohibiting or restricting circumvention of such 173 | measures. 174 | 175 | When you convey a covered work, you waive any legal power to forbid 176 | circumvention of technological measures to the extent such circumvention 177 | is effected by exercising rights under this License with respect to 178 | the covered work, and you disclaim any intention to limit operation or 179 | modification of the work as a means of enforcing, against the work's 180 | users, your or third parties' legal rights to forbid circumvention of 181 | technological measures. 182 | 183 | 4. Conveying Verbatim Copies. 184 | 185 | You may convey verbatim copies of the Program's source code as you 186 | receive it, in any medium, provided that you conspicuously and 187 | appropriately publish on each copy an appropriate copyright notice; 188 | keep intact all notices stating that this License and any 189 | non-permissive terms added in accord with section 7 apply to the code; 190 | keep intact all notices of the absence of any warranty; and give all 191 | recipients a copy of this License along with the Program. 192 | 193 | You may charge any price or no price for each copy that you convey, 194 | and you may offer support or warranty protection for a fee. 195 | 196 | 5. Conveying Modified Source Versions. 197 | 198 | You may convey a work based on the Program, or the modifications to 199 | produce it from the Program, in the form of source code under the 200 | terms of section 4, provided that you also meet all of these conditions: 201 | 202 | a) The work must carry prominent notices stating that you modified 203 | it, and giving a relevant date. 204 | 205 | b) The work must carry prominent notices stating that it is 206 | released under this License and any conditions added under section 207 | 7. This requirement modifies the requirement in section 4 to 208 | "keep intact all notices". 209 | 210 | c) You must license the entire work, as a whole, under this 211 | License to anyone who comes into possession of a copy. This 212 | License will therefore apply, along with any applicable section 7 213 | additional terms, to the whole of the work, and all its parts, 214 | regardless of how they are packaged. This License gives no 215 | permission to license the work in any other way, but it does not 216 | invalidate such permission if you have separately received it. 217 | 218 | d) If the work has interactive user interfaces, each must display 219 | Appropriate Legal Notices; however, if the Program has interactive 220 | interfaces that do not display Appropriate Legal Notices, your 221 | work need not make them do so. 222 | 223 | A compilation of a covered work with other separate and independent 224 | works, which are not by their nature extensions of the covered work, 225 | and which are not combined with it such as to form a larger program, 226 | in or on a volume of a storage or distribution medium, is called an 227 | "aggregate" if the compilation and its resulting copyright are not 228 | used to limit the access or legal rights of the compilation's users 229 | beyond what the individual works permit. Inclusion of a covered work 230 | in an aggregate does not cause this License to apply to the other 231 | parts of the aggregate. 232 | 233 | 6. Conveying Non-Source Forms. 234 | 235 | You may convey a covered work in object code form under the terms 236 | of sections 4 and 5, provided that you also convey the 237 | machine-readable Corresponding Source under the terms of this License, 238 | in one of these ways: 239 | 240 | a) Convey the object code in, or embodied in, a physical product 241 | (including a physical distribution medium), accompanied by the 242 | Corresponding Source fixed on a durable physical medium 243 | customarily used for software interchange. 244 | 245 | b) Convey the object code in, or embodied in, a physical product 246 | (including a physical distribution medium), accompanied by a 247 | written offer, valid for at least three years and valid for as 248 | long as you offer spare parts or customer support for that product 249 | model, to give anyone who possesses the object code either (1) a 250 | copy of the Corresponding Source for all the software in the 251 | product that is covered by this License, on a durable physical 252 | medium customarily used for software interchange, for a price no 253 | more than your reasonable cost of physically performing this 254 | conveying of source, or (2) access to copy the 255 | Corresponding Source from a network server at no charge. 256 | 257 | c) Convey individual copies of the object code with a copy of the 258 | written offer to provide the Corresponding Source. This 259 | alternative is allowed only occasionally and noncommercially, and 260 | only if you received the object code with such an offer, in accord 261 | with subsection 6b. 262 | 263 | d) Convey the object code by offering access from a designated 264 | place (gratis or for a charge), and offer equivalent access to the 265 | Corresponding Source in the same way through the same place at no 266 | further charge. You need not require recipients to copy the 267 | Corresponding Source along with the object code. If the place to 268 | copy the object code is a network server, the Corresponding Source 269 | may be on a different server (operated by you or a third party) 270 | that supports equivalent copying facilities, provided you maintain 271 | clear directions next to the object code saying where to find the 272 | Corresponding Source. Regardless of what server hosts the 273 | Corresponding Source, you remain obligated to ensure that it is 274 | available for as long as needed to satisfy these requirements. 275 | 276 | e) Convey the object code using peer-to-peer transmission, provided 277 | you inform other peers where the object code and Corresponding 278 | Source of the work are being offered to the general public at no 279 | charge under subsection 6d. 280 | 281 | A separable portion of the object code, whose source code is excluded 282 | from the Corresponding Source as a System Library, need not be 283 | included in conveying the object code work. 284 | 285 | A "User Product" is either (1) a "consumer product", which means any 286 | tangible personal property which is normally used for personal, family, 287 | or household purposes, or (2) anything designed or sold for incorporation 288 | into a dwelling. In determining whether a product is a consumer product, 289 | doubtful cases shall be resolved in favor of coverage. For a particular 290 | product received by a particular user, "normally used" refers to a 291 | typical or common use of that class of product, regardless of the status 292 | of the particular user or of the way in which the particular user 293 | actually uses, or expects or is expected to use, the product. A product 294 | is a consumer product regardless of whether the product has substantial 295 | commercial, industrial or non-consumer uses, unless such uses represent 296 | the only significant mode of use of the product. 297 | 298 | "Installation Information" for a User Product means any methods, 299 | procedures, authorization keys, or other information required to install 300 | and execute modified versions of a covered work in that User Product from 301 | a modified version of its Corresponding Source. The information must 302 | suffice to ensure that the continued functioning of the modified object 303 | code is in no case prevented or interfered with solely because 304 | modification has been made. 305 | 306 | If you convey an object code work under this section in, or with, or 307 | specifically for use in, a User Product, and the conveying occurs as 308 | part of a transaction in which the right of possession and use of the 309 | User Product is transferred to the recipient in perpetuity or for a 310 | fixed term (regardless of how the transaction is characterized), the 311 | Corresponding Source conveyed under this section must be accompanied 312 | by the Installation Information. But this requirement does not apply 313 | if neither you nor any third party retains the ability to install 314 | modified object code on the User Product (for example, the work has 315 | been installed in ROM). 316 | 317 | The requirement to provide Installation Information does not include a 318 | requirement to continue to provide support service, warranty, or updates 319 | for a work that has been modified or installed by the recipient, or for 320 | the User Product in which it has been modified or installed. Access to a 321 | network may be denied when the modification itself materially and 322 | adversely affects the operation of the network or violates the rules and 323 | protocols for communication across the network. 324 | 325 | Corresponding Source conveyed, and Installation Information provided, 326 | in accord with this section must be in a format that is publicly 327 | documented (and with an implementation available to the public in 328 | source code form), and must require no special password or key for 329 | unpacking, reading or copying. 330 | 331 | 7. Additional Terms. 332 | 333 | "Additional permissions" are terms that supplement the terms of this 334 | License by making exceptions from one or more of its conditions. 335 | Additional permissions that are applicable to the entire Program shall 336 | be treated as though they were included in this License, to the extent 337 | that they are valid under applicable law. If additional permissions 338 | apply only to part of the Program, that part may be used separately 339 | under those permissions, but the entire Program remains governed by 340 | this License without regard to the additional permissions. 341 | 342 | When you convey a copy of a covered work, you may at your option 343 | remove any additional permissions from that copy, or from any part of 344 | it. (Additional permissions may be written to require their own 345 | removal in certain cases when you modify the work.) You may place 346 | additional permissions on material, added by you to a covered work, 347 | for which you have or can give appropriate copyright permission. 348 | 349 | Notwithstanding any other provision of this License, for material you 350 | add to a covered work, you may (if authorized by the copyright holders of 351 | that material) supplement the terms of this License with terms: 352 | 353 | a) Disclaiming warranty or limiting liability differently from the 354 | terms of sections 15 and 16 of this License; or 355 | 356 | b) Requiring preservation of specified reasonable legal notices or 357 | author attributions in that material or in the Appropriate Legal 358 | Notices displayed by works containing it; or 359 | 360 | c) Prohibiting misrepresentation of the origin of that material, or 361 | requiring that modified versions of such material be marked in 362 | reasonable ways as different from the original version; or 363 | 364 | d) Limiting the use for publicity purposes of names of licensors or 365 | authors of the material; or 366 | 367 | e) Declining to grant rights under trademark law for use of some 368 | trade names, trademarks, or service marks; or 369 | 370 | f) Requiring indemnification of licensors and authors of that 371 | material by anyone who conveys the material (or modified versions of 372 | it) with contractual assumptions of liability to the recipient, for 373 | any liability that these contractual assumptions directly impose on 374 | those licensors and authors. 375 | 376 | All other non-permissive additional terms are considered "further 377 | restrictions" within the meaning of section 10. If the Program as you 378 | received it, or any part of it, contains a notice stating that it is 379 | governed by this License along with a term that is a further 380 | restriction, you may remove that term. If a license document contains 381 | a further restriction but permits relicensing or conveying under this 382 | License, you may add to a covered work material governed by the terms 383 | of that license document, provided that the further restriction does 384 | not survive such relicensing or conveying. 385 | 386 | If you add terms to a covered work in accord with this section, you 387 | must place, in the relevant source files, a statement of the 388 | additional terms that apply to those files, or a notice indicating 389 | where to find the applicable terms. 390 | 391 | Additional terms, permissive or non-permissive, may be stated in the 392 | form of a separately written license, or stated as exceptions; 393 | the above requirements apply either way. 394 | 395 | 8. Termination. 396 | 397 | You may not propagate or modify a covered work except as expressly 398 | provided under this License. Any attempt otherwise to propagate or 399 | modify it is void, and will automatically terminate your rights under 400 | this License (including any patent licenses granted under the third 401 | paragraph of section 11). 402 | 403 | However, if you cease all violation of this License, then your 404 | license from a particular copyright holder is reinstated (a) 405 | provisionally, unless and until the copyright holder explicitly and 406 | finally terminates your license, and (b) permanently, if the copyright 407 | holder fails to notify you of the violation by some reasonable means 408 | prior to 60 days after the cessation. 409 | 410 | Moreover, your license from a particular copyright holder is 411 | reinstated permanently if the copyright holder notifies you of the 412 | violation by some reasonable means, this is the first time you have 413 | received notice of violation of this License (for any work) from that 414 | copyright holder, and you cure the violation prior to 30 days after 415 | your receipt of the notice. 416 | 417 | Termination of your rights under this section does not terminate the 418 | licenses of parties who have received copies or rights from you under 419 | this License. If your rights have been terminated and not permanently 420 | reinstated, you do not qualify to receive new licenses for the same 421 | material under section 10. 422 | 423 | 9. Acceptance Not Required for Having Copies. 424 | 425 | You are not required to accept this License in order to receive or 426 | run a copy of the Program. Ancillary propagation of a covered work 427 | occurring solely as a consequence of using peer-to-peer transmission 428 | to receive a copy likewise does not require acceptance. However, 429 | nothing other than this License grants you permission to propagate or 430 | modify any covered work. These actions infringe copyright if you do 431 | not accept this License. Therefore, by modifying or propagating a 432 | covered work, you indicate your acceptance of this License to do so. 433 | 434 | 10. Automatic Licensing of Downstream Recipients. 435 | 436 | Each time you convey a covered work, the recipient automatically 437 | receives a license from the original licensors, to run, modify and 438 | propagate that work, subject to this License. You are not responsible 439 | for enforcing compliance by third parties with this License. 440 | 441 | An "entity transaction" is a transaction transferring control of an 442 | organization, or substantially all assets of one, or subdividing an 443 | organization, or merging organizations. If propagation of a covered 444 | work results from an entity transaction, each party to that 445 | transaction who receives a copy of the work also receives whatever 446 | licenses to the work the party's predecessor in interest had or could 447 | give under the previous paragraph, plus a right to possession of the 448 | Corresponding Source of the work from the predecessor in interest, if 449 | the predecessor has it or can get it with reasonable efforts. 450 | 451 | You may not impose any further restrictions on the exercise of the 452 | rights granted or affirmed under this License. For example, you may 453 | not impose a license fee, royalty, or other charge for exercise of 454 | rights granted under this License, and you may not initiate litigation 455 | (including a cross-claim or counterclaim in a lawsuit) alleging that 456 | any patent claim is infringed by making, using, selling, offering for 457 | sale, or importing the Program or any portion of it. 458 | 459 | 11. Patents. 460 | 461 | A "contributor" is a copyright holder who authorizes use under this 462 | License of the Program or a work on which the Program is based. The 463 | work thus licensed is called the contributor's "contributor version". 464 | 465 | A contributor's "essential patent claims" are all patent claims 466 | owned or controlled by the contributor, whether already acquired or 467 | hereafter acquired, that would be infringed by some manner, permitted 468 | by this License, of making, using, or selling its contributor version, 469 | but do not include claims that would be infringed only as a 470 | consequence of further modification of the contributor version. For 471 | purposes of this definition, "control" includes the right to grant 472 | patent sublicenses in a manner consistent with the requirements of 473 | this License. 474 | 475 | Each contributor grants you a non-exclusive, worldwide, royalty-free 476 | patent license under the contributor's essential patent claims, to 477 | make, use, sell, offer for sale, import and otherwise run, modify and 478 | propagate the contents of its contributor version. 479 | 480 | In the following three paragraphs, a "patent license" is any express 481 | agreement or commitment, however denominated, not to enforce a patent 482 | (such as an express permission to practice a patent or covenant not to 483 | sue for patent infringement). To "grant" such a patent license to a 484 | party means to make such an agreement or commitment not to enforce a 485 | patent against the party. 486 | 487 | If you convey a covered work, knowingly relying on a patent license, 488 | and the Corresponding Source of the work is not available for anyone 489 | to copy, free of charge and under the terms of this License, through a 490 | publicly available network server or other readily accessible means, 491 | then you must either (1) cause the Corresponding Source to be so 492 | available, or (2) arrange to deprive yourself of the benefit of the 493 | patent license for this particular work, or (3) arrange, in a manner 494 | consistent with the requirements of this License, to extend the patent 495 | license to downstream recipients. "Knowingly relying" means you have 496 | actual knowledge that, but for the patent license, your conveying the 497 | covered work in a country, or your recipient's use of the covered work 498 | in a country, would infringe one or more identifiable patents in that 499 | country that you have reason to believe are valid. 500 | 501 | If, pursuant to or in connection with a single transaction or 502 | arrangement, you convey, or propagate by procuring conveyance of, a 503 | covered work, and grant a patent license to some of the parties 504 | receiving the covered work authorizing them to use, propagate, modify 505 | or convey a specific copy of the covered work, then the patent license 506 | you grant is automatically extended to all recipients of the covered 507 | work and works based on it. 508 | 509 | A patent license is "discriminatory" if it does not include within 510 | the scope of its coverage, prohibits the exercise of, or is 511 | conditioned on the non-exercise of one or more of the rights that are 512 | specifically granted under this License. You may not convey a covered 513 | work if you are a party to an arrangement with a third party that is 514 | in the business of distributing software, under which you make payment 515 | to the third party based on the extent of your activity of conveying 516 | the work, and under which the third party grants, to any of the 517 | parties who would receive the covered work from you, a discriminatory 518 | patent license (a) in connection with copies of the covered work 519 | conveyed by you (or copies made from those copies), or (b) primarily 520 | for and in connection with specific products or compilations that 521 | contain the covered work, unless you entered into that arrangement, 522 | or that patent license was granted, prior to 28 March 2007. 523 | 524 | Nothing in this License shall be construed as excluding or limiting 525 | any implied license or other defenses to infringement that may 526 | otherwise be available to you under applicable patent law. 527 | 528 | 12. No Surrender of Others' Freedom. 529 | 530 | If conditions are imposed on you (whether by court order, agreement or 531 | otherwise) that contradict the conditions of this License, they do not 532 | excuse you from the conditions of this License. If you cannot convey a 533 | covered work so as to satisfy simultaneously your obligations under this 534 | License and any other pertinent obligations, then as a consequence you may 535 | not convey it at all. For example, if you agree to terms that obligate you 536 | to collect a royalty for further conveying from those to whom you convey 537 | the Program, the only way you could satisfy both those terms and this 538 | License would be to refrain entirely from conveying the Program. 539 | 540 | 13. Remote Network Interaction; Use with the GNU General Public License. 541 | 542 | Notwithstanding any other provision of this License, if you modify the 543 | Program, your modified version must prominently offer all users 544 | interacting with it remotely through a computer network (if your version 545 | supports such interaction) an opportunity to receive the Corresponding 546 | Source of your version by providing access to the Corresponding Source 547 | from a network server at no charge, through some standard or customary 548 | means of facilitating copying of software. This Corresponding Source 549 | shall include the Corresponding Source for any work covered by version 3 550 | of the GNU General Public License that is incorporated pursuant to the 551 | following paragraph. 552 | 553 | Notwithstanding any other provision of this License, you have 554 | permission to link or combine any covered work with a work licensed 555 | under version 3 of the GNU General Public License into a single 556 | combined work, and to convey the resulting work. The terms of this 557 | License will continue to apply to the part which is the covered work, 558 | but the work with which it is combined will remain governed by version 559 | 3 of the GNU General Public License. 560 | 561 | 14. Revised Versions of this License. 562 | 563 | The Free Software Foundation may publish revised and/or new versions of 564 | the GNU Affero General Public License from time to time. Such new versions 565 | will be similar in spirit to the present version, but may differ in detail to 566 | address new problems or concerns. 567 | 568 | Each version is given a distinguishing version number. If the 569 | Program specifies that a certain numbered version of the GNU Affero General 570 | Public License "or any later version" applies to it, you have the 571 | option of following the terms and conditions either of that numbered 572 | version or of any later version published by the Free Software 573 | Foundation. If the Program does not specify a version number of the 574 | GNU Affero General Public License, you may choose any version ever published 575 | by the Free Software Foundation. 576 | 577 | If the Program specifies that a proxy can decide which future 578 | versions of the GNU Affero General Public License can be used, that proxy's 579 | public statement of acceptance of a version permanently authorizes you 580 | to choose that version for the Program. 581 | 582 | Later license versions may give you additional or different 583 | permissions. However, no additional obligations are imposed on any 584 | author or copyright holder as a result of your choosing to follow a 585 | later version. 586 | 587 | 15. Disclaimer of Warranty. 588 | 589 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 590 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 591 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 592 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 593 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 594 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 595 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 596 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 597 | 598 | 16. Limitation of Liability. 599 | 600 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 601 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 602 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 603 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 604 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 605 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 606 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 607 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 608 | SUCH DAMAGES. 609 | 610 | 17. Interpretation of Sections 15 and 16. 611 | 612 | If the disclaimer of warranty and limitation of liability provided 613 | above cannot be given local legal effect according to their terms, 614 | reviewing courts shall apply local law that most closely approximates 615 | an absolute waiver of all civil liability in connection with the 616 | Program, unless a warranty or assumption of liability accompanies a 617 | copy of the Program in return for a fee. 618 | 619 | END OF TERMS AND CONDITIONS 620 | 621 | How to Apply These Terms to Your New Programs 622 | 623 | If you develop a new program, and you want it to be of the greatest 624 | possible use to the public, the best way to achieve this is to make it 625 | free software which everyone can redistribute and change under these terms. 626 | 627 | To do so, attach the following notices to the program. It is safest 628 | to attach them to the start of each source file to most effectively 629 | state the exclusion of warranty; and each file should have at least 630 | the "copyright" line and a pointer to where the full notice is found. 631 | 632 | 633 | Copyright (C) 634 | 635 | This program is free software: you can redistribute it and/or modify 636 | it under the terms of the GNU Affero General Public License as published 637 | by the Free Software Foundation, either version 3 of the License, or 638 | (at your option) any later version. 639 | 640 | This program is distributed in the hope that it will be useful, 641 | but WITHOUT ANY WARRANTY; without even the implied warranty of 642 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 643 | GNU Affero General Public License for more details. 644 | 645 | You should have received a copy of the GNU Affero General Public License 646 | along with this program. If not, see . 647 | 648 | Also add information on how to contact you by electronic and paper mail. 649 | 650 | If your software can interact with users remotely through a computer 651 | network, you should also make sure that it provides a way for users to 652 | get its source. For example, if your program is a web application, its 653 | interface could display a "Source" link that leads users to an archive 654 | of the code. There are many ways you could offer source, and different 655 | solutions will be better for different programs; see section 13 for the 656 | specific requirements. 657 | 658 | You should also get your employer (if you work as a programmer) or school, 659 | if any, to sign a "copyright disclaimer" for the program, if necessary. 660 | For more information on this, and how to apply and follow the GNU AGPL, see 661 | . 662 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Overview 2 | peafl64 is a static instrumentation tool for x64 PEs in Windows. 3 | Static instrumentation is the practice of editing executable files and adding code to specific locations in them. 4 | The instrumentation adds code to the start of every basic block in the binary, logging the execution flow in an AFL-compatible way. 5 | It allows us to fuzz binaries in Usermode (using WinAFL) and Kernelmode (using kAFL) without access to their source code. 6 | 7 | There are other ways of fuzzing Windows binaries; in this project we chose to focus on static instrumentation because it's the fastest method. 8 | 9 | This project builds on the [pe-afl](https://github.com/wmliang/pe-afl) tool by wmliang, with added x64 support. 10 | 11 | ## Features 12 | * Full Windows x64 binaries support 13 | * High performance 14 | * Supports instrumenting with process ID or thread ID filtering 15 | * Handles relocations, exception tables, relative instructions, jump tables, imports, exports and more 16 | * Compatible with WinAFL (headers included) and kAFL 17 | 18 | ## Usage 19 | ### IDA Analysis 20 | The instrumentation script requires an IDA analysis output. 21 | To create it, run the provided `ida_dumper.py` script in IDA. 22 | The script requires IDA 7+ and python3.8+. 23 | 24 | ### Instrumentation 25 | ``` 26 | usage: pe_afl.py [-h] [-n] [-cb] [-tf] [-te THREAD_ENTRY] [-nt NTOSKRNL] [-e ENTRY] [-l ENLARGE] [-v] [-lf] pefile ida_dump 27 | 28 | positional arguments: 29 | pefile Target PE file for instrumentation 30 | ida_dump dump.json from IDA (created by ida_dumper.py) 31 | 32 | optional arguments: 33 | -h, --help show this help message and exit 34 | -n, --nop Instrument with NOPs for testing 35 | -cb, --callback Instrument with a callback, which is in the helper driver that's written in C 36 | -tf, --thread-filter Driver instrumentation that filters only on thread ID (must use "-te" with this option) 37 | -te THREAD_ENTRY, --thread-entry THREAD_ENTRY 38 | The address (RVA) of the thread's initialization function 39 | -nt NTOSKRNL, --ntoskrnl NTOSKRNL 40 | ntoskrnl.exe path for offset extraction (non-optional if instrumenting a driver) 41 | -e ENTRY, --entry ENTRY 42 | Inject code on entry point, ie. -e9090 43 | -l ENLARGE, --enlarge ENLARGE 44 | Enlarge factor for sections, default=4 45 | -v, --verbose Print debug log 46 | -lf, --logfile Print log to pe-afl64.log rather than stream 47 | ``` 48 | 49 | **Instrumenting usermode binary with NOPs** 50 | ``` 51 | PS pe-afl-64> python .\pe_afl.py -n C:\Work\cmd.exe C:\Work\cmd.exe.dump.json 52 | [*] User-mode binary is being instrumented 53 | [*] Single-thread instrument is on 54 | [*] Preparing new sections 55 | [*] Added section .text^ 56 | [*] Added section .cov 57 | [*] Expanding relative jumps 58 | [*] Expanded 3874 of 14353 branches 59 | [*] Building address map 60 | [*] Updating relative instructions 61 | [*] Updating relocations... 62 | [*] Updating Export table 63 | [*] Updating load config 64 | [*] Updating exception records 65 | [*] Updating the PE headers 66 | [*] Finalizing... 67 | [*] Creating instrumented code 68 | [*] Writing address mapping to C:\Work\cmd.exe.mapping.txt 69 | [*] Updating jump tables 70 | [*] Updated .text 71 | ... 72 | [*] Removing temporary PE files 73 | [*] Fixing PE checksum 74 | [*] Instrumented binary saved to: C:\Work\cmd.instrumented.exe 75 | ``` 76 | **Instrumenting kernelmode binary with process ID filtering** 77 | ``` 78 | python .\pe_afl.py -l 6 -nt "C:\Work\ntoskrnl.exe" "C:\Work\driver.sys" "C:\Work\driver.sys.dump.json" 79 | ``` 80 | **Instrumenting kernelmode binary with thread ID filtering and verbose output** 81 | ``` 82 | python .\pe_afl.py -v -tf -te 0x40000 -l 6 -nt "C:\Work\ntoskrnl.exe" "C:\Work\ntoskrnl.exe" "C:\Work\ntoskrnl.exe.dump.json" 83 | ``` 84 | 85 | ## How to replace Windows drivers 86 | First you'll need to check if your machine is booting using BIOS or UEFI. 87 | For Hyper-V machines: Gen 1 machines are BIOS based, and the Gen 2 are UEFI based. 88 | If your machine is BIOS based then you need to patch `winload.exe`: 89 | * Get a copy of winload.exe from your VM and find the function `ImgpValidateImageHash` in it 90 | * Patch the return value to always return 0 in rax. For example replace `mov eax, edi` with `xor eax, eax` in the last code block of the function 91 | * Copy the patched winload to system32 folder and run `bcdedit /set path \Windows\system32\winload2.exe` 92 | 93 | If your machine is booted using UEFI then use [EfiGuard](https://github.com/Mattiwatti/EfiGuard) util to patch `winload.efi`. 94 | Command cheatsheet if using Hyper-V Manager: 95 | 1. Create a new hard drive for the Virtual Machine 96 | 2. Use the supplied FAT.vhdx in the Tools folder (or create one yourself) containing the UefiShell+EfiGuard module with the new hard driver 97 | 3. Change the machine's boot order, so the new hard drive is first in order 98 | 4. After boot, while in the UefiShell run the following commands: 99 | ``` 100 | FS1: 101 | cd EFI/Boot 102 | Load EfiGuard.Dxe.efi 103 | FS0: 104 | \EFI\Boot\bootx64.efi 105 | ``` 106 | 107 | Then instrument your driver of choice. To load an instrumented driver on a Windows machine it must be signed, 108 | and a self-signing certificate is enough to pass the OS's demands: 109 | ```shell 110 | # In elevated powershell terminal 111 | $c = New-SelfSignedCertificate -Type CodeSigningCert -KeyUsage DigitalSignature -Subject 'CN=Microsoft Windows, O=Microsoft Corporation, L=Redmond, S=Washington, C=US' 112 | Set-AuthenticodeSignature .\driver.instrumented.sys -Certificate $c -Force 113 | ``` 114 | If the driver you instrumented is already used by the system, use these commands (as admin) to replace the driver's file: 115 | ```batch 116 | set NAME=mydriver.sys 117 | icacls %NAME% /save C:\windows\temp\%NAME%.icacls 118 | takeown /F %NAME% 119 | icacls %NAME% /grant Everyone:F 120 | move %NAME% %NAME%.bak 121 | move instrumented_driver.sys %NAME% 122 | icacls . /restore C:\windows\temp\%NAME%.icacls 123 | ``` 124 | 125 | Then run: 126 | ```batch 127 | bcdedit /set recoveryenabled no 128 | bcdedit /set nointegritychecks on 129 | shutdown -t 0 -r 130 | ``` 131 | 132 | 133 | ## WinAFL Fuzzing 134 | Integration with WinAFL is by compiling a harness using the provided headers. 135 | Alongside the headers there's `example.c`, which is a sample program that shows how to use them. 136 | The provided headers are a slight modification of the headers that are already provided by WinAFL to integrate with 137 | another Static Binary Instrumentation tool called Syzygy. 138 | 139 | ## kAFL Fuzzing 140 | The way we integrate with kAFL is pretty simple. 141 | Normally, a kAFL harness is running on a virtual machine and talks to the fuzzer's frontend using special "hypercalls". 142 | These hypercalls tell the fuzzer to do many things, among them is to load coverage data from IntelPT and parse it as an AFL bitmap. 143 | Because peafl64 makes IntelPT tracing obsolete, we must prepare a way to transmit the coverage data to the fuzzer. 144 | Therefore, we expanded qemu and kvm with "hypercalls" that allow the (usermode) harness that runs in a VM to send the coverage data it collected using the helper driver. 145 | 146 | ### ESXi Setup 147 | This is specifically about the setup on ESXi but should be relevant for other virtualization platforms like AWS. 148 | The setup is pretty simple: 149 | * Create an Ubuntu machine 150 | * Make sure "Expose hardware assisted virtualization to the guest OS" is enabled in the machine's CPU configuration 151 | * Clone the sbi_kAFL repo 152 | * Install kAFL normally as instructed in the kAFL repo, but instead of `install.sh qemu` step, run `install.sh qemu_sbi` 153 | 154 | To fuzz using kAFL and peafl64, we need to setup a fuzzing machine: 155 | * Compile the helper driver and sign it 156 | * Compile a harness using the headers provided with our kAFL fork 157 | * On the VM - load the helper driver 158 | * Run the kAFL's loader 159 | 160 | Other than that, fuzzing with our kAFL fork is the same as normal fuzzing with kAFL. 161 | 162 | 163 | ## Execution Flow 164 | Steps Overview: 165 | 1. Determine insertion points for instrumentation code 166 | 2. Locate instructions and structures that will need adjustments 167 | 3. Insert the instrumentation code to the requested functions 168 | 4. Adjust: 169 | * Relative instructions 170 | * Jump tables 171 | * Exception handlers 172 | * PE headers 173 | * Various PE configurations (load config) 174 | 5. Rebuilding the PE with the instrumented sections 175 | 176 | 177 | First, we use IDA to find and analyze the instructions and locations of interest. 178 | The analysis creates a `dump.json` file that contains all that information in a json format. 179 | `instrument.py` contains the instrumentation logic, and the flow itself is outlined in the `process_pe` function. 180 | To instrument the binary, we duplicate its executable sections, where all the instrumented code will be found. 181 | Next, we process all the relative instructions and determine how and if we need to handle them. 182 | Suppose we have a short jmp from address X to address Y. We insert instrumentation code between X and Y, and now the target is at address Z (`Z = Y + len(instrumentation)`). 183 | That means we have to modify the jump. 184 | If address Z is now located outside the range of short jmps - we need to transform the jump from `short` to `far`. (instrument.py:expand_relative_instructions) 185 | For example: 186 | * `short jmp 0x57` (eb 55) -> `near jmp 0x604` (e9 ff 05 00 00) 187 | 188 | That's also the case for instructions like `call`, `loop` and conditional jumps. 189 | Another common case that needs handling is rip-relative instructions that were introduced in x64 assembly: 190 | * `call [rip+0x1000]` 191 | 192 | We update the following things to point to the instrumented code: relocation table, export table, load configuration, the TLS directory and exception records. 193 | The updates are done by updating all addresses from the original sections to their instrumented counterparts. (instrument.py:update_addr) 194 | The headers are updated to reflect the changes to the PE structure - added sections, changed entrypoint and PE size. (instrument.py:update_pe_headers) 195 | 196 | Additionally, peafl64 is able to instrument the Windows Kernel. To achieve that, it parses and updated the Dynamic Value Relocation Table (DVRT) and the SSDT, and handles PatchGuard specifics. 197 | 198 | The DVRT is a compiler generated table which describes the locations of addresses that need to be changed when loading the PE. 199 | It's used to improve KASLR and helps mitigate the Spectre vulnerability ([1](https://xlab.tencent.com/en/2016/11/02/return-flow-guard/), [2](https://techcommunity.microsoft.com/t5/windows-kernel-internals-blog/mitigating-spectre-variant-2-with-retpoline-on-windows/ba-p/295618), [3](https://github.com/saferwall/pe/blob/f7468c51a591d5ba47480704c29a1462fd1d4214/loadconfig.go)). 200 | The DVRT is parsed using a set of classes that mimic the structure of the table (drt.py; instrument.py:get_updated_dynamic_relocs) 201 | 202 | Handling and updating the SSDT wasn't as straight-forward. 203 | Its address is not exported, so we had to either rely on symbols or on heuristics to locate it. 204 | Our solution uses a heuristic approach, using `NtWaitForSingleObject` as a constant marker for all Windows 10 versions and finding the address of the SSDT relatively to it. (instrument.py:ntoskrnl_update_KiServiceTable) 205 | This approach works for all Windows 10 Kernels, but will need to be adjusted for other kernel versions. 206 | 207 | ## TODO 208 | 209 | * ~~support x64~~ 210 | * New exception handlers (cxx4) 211 | * Improve IDA dumper performance 212 | * Fully parse LOAD_CONFIG struct 213 | * Add tests 214 | * Support more Windows Kernel versions out of the box 215 | * Integrate with Nyx (the new kAFL) 216 | 217 | -------------------------------------------------------------------------------- /Tools/FAT.vhdx.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sentinel-One/peafl64/dd2ac42d1980822c8f9e481dc9cefb2fa4a76970/Tools/FAT.vhdx.zip -------------------------------------------------------------------------------- /asm_stubs64.py: -------------------------------------------------------------------------------- 1 | from keystone.keystone import Ks, KS_ARCH_X86, KS_MODE_64 2 | 3 | import pefile 4 | 5 | 6 | def asm(code) -> bytearray: 7 | ks = Ks(KS_ARCH_X86, KS_MODE_64) 8 | return bytearray(ks.asm(code)[0]) 9 | 10 | 11 | def extract_exported_function_bytes(exporting_pe: pefile.PE, funcname: bytes) -> bytes: 12 | """ 13 | Extracts the instructions bytes of the wanted function from ntoskrnl.exe. 14 | :param exporting_pe: PE object for the pe file with the wanted exported function 15 | :param funcname: Function name 16 | :return: bytes 17 | """ 18 | matching_symbols = [sym for sym in exporting_pe.DIRECTORY_ENTRY_EXPORT.symbols if sym.name == funcname] 19 | if not matching_symbols: 20 | raise ValueError( 21 | f"The function {funcname.decode('utf-8')} wasn't found in the given kernel file, can't build shellcode") 22 | export = matching_symbols[0] 23 | # Read arbitrary large amount of bytes 24 | func_data = exporting_pe.get_data(export.address, 200) 25 | # Return only the instructions up until the "ret" (0xc3) 26 | return func_data[:func_data.find(b'\xc3')] 27 | 28 | 29 | ## Magics 30 | C_ADDR1 = C_ADDR2 = 0x4444444444444444 31 | M_PREV_LOC1 = M_PREV_LOC2 = M_PID = M_AREA_PTR = M_CALLBACK = M_TID = MAGIC = 0x5555555555555555 32 | 33 | # Common prefix for our shellcodes, saves our used registers 34 | sc_prefix = ''' 35 | push rbx 36 | push rax 37 | pushfq 38 | ''' 39 | 40 | # Common suffix for our shellcodes, restores our used registers 41 | sc_suffix = ''' 42 | popfq 43 | pop rax 44 | pop rbx 45 | ''' 46 | 47 | # Define different shellcodes 48 | asm_nop = bytearray(b'\x90' * 0x6) 49 | 50 | # Simple shellcode that updates the AFL's bitmap 51 | asm_single = f''' 52 | mov rax, [{hex(M_PREV_LOC1)}] # __afl_prev_loc @ .cov+0x10000 53 | mov rbx, {hex(C_ADDR1)} 54 | xor rbx, rax 55 | mov rax, {hex(M_AREA_PTR)} # __afl_area_ptr @ .cov 56 | inc byte ptr [rbx + rax] 57 | mov rax, {hex(C_ADDR2)} 58 | mov [{hex(M_PREV_LOC2)}], rax 59 | ''' 60 | # Multi threaded shellcode is not yet implemented 61 | asm_multi = asm_nop 62 | 63 | # Shellcode that only updates the AFL's bitmap if the current pid matches the harness's pid 64 | asm_filter = f''' 65 | mov rbx, rax 66 | mov rax, [{hex(M_PID)}] # pid @ .cov+0x10000+0x10 67 | cmp rbx, rax # rax should contain our pid from PsGetCurrentProcessId 68 | jne skip 69 | mov rax, [{hex(M_PREV_LOC1)}] # __afl_prev_loc @ .cov+0x10000 70 | mov rbx, {hex(C_ADDR1)} 71 | xor rbx, rax 72 | mov rax, {hex(M_AREA_PTR)} # __afl_area_ptr @ .cov 73 | inc byte ptr [rbx + rax] 74 | mov rax, {hex(C_ADDR2)} 75 | mov [{hex(M_PREV_LOC2)}], rax 76 | skip: 77 | ''' 78 | 79 | # TODO Shellcode that calls an external function for each instrumented block 80 | # Currently not implemented and tested 81 | asm_callback = f''' 82 | mov rbx, rax 83 | mov rax, [{hex(M_PID)}] # pid @ .cov+0x10000+0x10 84 | cmp rbx, rax # rax should contain our pid from PsGetCurrentProcessId 85 | jne skip 86 | mov rax, [{hex(M_CALLBACK)}] # callback @ .cov+0x10000+0x20 87 | call qword ptr [rax] 88 | skip: 89 | ''' 90 | 91 | # Shellcode that initializes the thread id for the next shellcode 92 | asm_thread_filter_init = f''' 93 | mov rbx, rax 94 | # read thread ID address and check if it's empty 95 | mov rax, [{hex(M_TID)}] # tid @ .cov+0x10000+0x30 96 | cmp rax, 0 97 | jne skip 98 | mov rax, rbx 99 | mov [{hex(M_TID)}], rax # tid @ .cov+0x10000+0x30 100 | # restore state 101 | skip: 102 | ''' 103 | 104 | # Shellcode that only updates the AFL's bitmap if the current thread id matches the harness's pid 105 | asm_thread_filter_generic = f''' 106 | # read desired thread id from memory and get current thread ID 107 | mov rbx, rax 108 | mov rax, [{hex(M_TID)}] # __afl_prev_loc @ .cov+0x10000 + 0x30 109 | cmp rax, rbx 110 | jne skip 111 | mov rax, [{hex(M_PREV_LOC1)}] # __afl_prev_loc @ .cov+0x10000 112 | mov rbx, {hex(C_ADDR1)} 113 | xor rbx, rax 114 | mov rax, {hex(M_AREA_PTR)} # __afl_area_ptr @ .cov 115 | inc byte ptr [rbx + rax] 116 | mov rax, {hex(C_ADDR2)} 117 | mov [{hex(M_PREV_LOC2)}], rax 118 | # restore state 119 | skip: 120 | ''' 121 | -------------------------------------------------------------------------------- /consts.py: -------------------------------------------------------------------------------- 1 | MAGIC_NAME_MARKER = b'^' 2 | ALT_MAGIC_NAME_MARKERS = [b'*', b'!', b'`', b'&', b'#', b'@', b'$', b'%'] 3 | RELOC_SEC_NAME = b'.reloc' 4 | -------------------------------------------------------------------------------- /drt.py: -------------------------------------------------------------------------------- 1 | import struct 2 | from pefile import PE 3 | from typing import List, Tuple 4 | 5 | 6 | # Consts 7 | # the offset of the fields in the load config struct, TODO: define (or find a python definition of) the full struct 8 | DYNAMIC_RELOC_TABLE_OFFSET_FIELD_OFFSET = 0xe0 9 | 10 | 11 | class DRTException(BaseException): 12 | pass 13 | 14 | 15 | class IMAGE_DYNAMIC_RELOCATION_TABLE: 16 | sizeof = 8 17 | 18 | def __init__(self): 19 | self.version: int = None 20 | self.size: int = None 21 | self.dynamic_relocations: List[IMAGE_DYNAMIC_RELOCATION] = None 22 | 23 | @classmethod 24 | def from_pe(cls, pe: PE) -> 'IMAGE_DYNAMIC_RELOCATION_TABLE': 25 | """ 26 | Creates a DRT from a PE file object 27 | :param pe: PE file object 28 | :return: IMAGE_DYNAMIC_RELOCATION_TABLE 29 | """ 30 | drt = cls() 31 | # TODO verify dir is not none is enough 32 | if not (hasattr(pe, 'DIRECTORY_ENTRY_LOAD_CONFIG') and pe.DIRECTORY_ENTRY_LOAD_CONFIG is not None): 33 | raise DRTException("PE has no load config!") 34 | load_config_base_offset = pe.DIRECTORY_ENTRY_LOAD_CONFIG.struct.get_file_offset() 35 | dynamic_reloc_table_offset = pe.get_dword_from_offset( 36 | load_config_base_offset + DYNAMIC_RELOC_TABLE_OFFSET_FIELD_OFFSET) 37 | dynamic_reloc_table_section = pe.get_word_from_offset( 38 | load_config_base_offset + DYNAMIC_RELOC_TABLE_OFFSET_FIELD_OFFSET + 4) 39 | if dynamic_reloc_table_section == 0 or dynamic_reloc_table_offset == 0: 40 | raise DRTException("PE ha no dynamic relocation table!") 41 | # the dynamic_reloc_table_section is the section that contains the DRT and all the offsets are relative to its base 42 | assert dynamic_reloc_table_section <= len( 43 | pe.sections), 'dynamic reloc table section is out of bounds for the PE' 44 | base_section_offset = pe.sections[dynamic_reloc_table_section - 1].get_PointerToRawData_adj() 45 | drt.version, drt.size = struct.unpack(' 'IMAGE_DYNAMIC_RELOCATION_TABLE': 61 | """ 62 | Creates a DRT from a list of dynamic relocations 63 | :param dynamic_relocations: List of dynamic relocations 64 | :return: DRT 65 | """ 66 | drt = cls() 67 | drt.version = 1 68 | drt.size = 0 69 | for dynamic_relocation in dynamic_relocations: 70 | drt.size += IMAGE_DYNAMIC_RELOCATION.sizeof + dynamic_relocation.base_reloc_size 71 | drt.dynamic_relocations = dynamic_relocations 72 | return drt 73 | 74 | def __repr__(self) -> str: 75 | return f"Version: {self.version} | Size: {hex(self.size)}\n" \ 76 | f"Dynamic Relocation Data: {self.dynamic_relocations}" 77 | 78 | def dump(self) -> bytearray: 79 | """ 80 | Dumps the dynamic relocation table into a packed struct (its raw form in the binary) 81 | :return: Packed DRT 82 | """ 83 | packed_drt = bytearray(struct.pack(' 'IMAGE_DYNAMIC_RELOCATION': 100 | """ 101 | Creates a IMAGE_DYNAMIC_RELOCATION from raw data 102 | :param raw_data: IMAGE_DYNAMIC_RELOCATION raw data 103 | :return: IMAGE_DYNAMIC_RELOCATION 104 | """ 105 | dynamic_reloc = cls() 106 | dynamic_reloc.symbol, dynamic_reloc.base_reloc_size = struct.unpack(' 'IMAGE_DYNAMIC_RELOCATION': 129 | dynamic_reloc = cls() 130 | dynamic_reloc.symbol = symbol 131 | dynamic_reloc.base_reloc_size = 0 132 | if base_relocations: 133 | dynamic_reloc.base_reloc_size = sum([reloc.size_of_block for reloc in base_relocations]) 134 | if function_override_header: 135 | dynamic_reloc.base_reloc_size += function_override_header.sizeof + function_override_header.func_override_size + function_override_header.bdd_info.sizeof + function_override_header.bdd_info.bdd_size 136 | dynamic_reloc.base_relocations = base_relocations 137 | dynamic_reloc.function_override_header = function_override_header 138 | return dynamic_reloc 139 | 140 | def __repr__(self) -> str: 141 | return f"Symbol: {hex(self.symbol)} | Base Reloc Size: {hex(self.base_reloc_size)} \n" \ 142 | f"Base Relocations: {self.base_relocations}\n" \ 143 | f"Function Override Info: {self.function_override_header}\n" 144 | 145 | def dump(self) -> bytearray: 146 | packed_dynamic_reloc = bytearray(struct.pack(' 'IMAGE_BASE_RELOCATION': 174 | """ 175 | Creates an IMAGE_BASE_RELOCATION object from the raw data 176 | :param raw_data: raw data of the IMAGE_BASE_RELOCATION 177 | :return: IMAGE_BASE_RELOCATION object 178 | """ 179 | reloc = cls() 180 | reloc.virtual_address, reloc.size_of_block = struct.unpack('> 12 194 | reloc.type_offsets.append((rva, offset_type)) 195 | 196 | reloc.has_padding = False 197 | # The last type offset is padding if there is an odd number of type offsets in the block 198 | if is_word_sized and reloc.type_offsets[-1][0] - reloc.virtual_address == 0: 199 | reloc.type_offsets = reloc.type_offsets[:-1] 200 | reloc.has_padding = True 201 | reloc.num_of_type_offsets -= 1 202 | 203 | return reloc 204 | 205 | @classmethod 206 | def from_data(cls, virtual_address: int, type_offsets: List[Tuple[int, int]], is_word_sized: bool = True) -> 'IMAGE_BASE_RELOCATION': 207 | """ 208 | Creates an IMAGE_BASE_RELOCATION object from the given data 209 | :param virtual_address: virtual address of the base relocation 210 | :param type_offsets: list of tuples of (rva, offset_type) 211 | :return: IMAGE_BASE_RELOCATION object 212 | """ 213 | base_reloc = cls() 214 | base_reloc.virtual_address = virtual_address 215 | base_reloc.is_word_sized = is_word_sized 216 | if is_word_sized: 217 | struct_size = 2 218 | else: 219 | struct_size = 4 220 | # If there is an odd number of type offsets, add a padding type offset 221 | if is_word_sized and len(type_offsets) % 2 != 0: 222 | base_reloc.num_of_type_offsets = len(type_offsets) + 1 223 | base_reloc.has_padding = True 224 | else: 225 | base_reloc.num_of_type_offsets = len(type_offsets) 226 | base_reloc.has_padding = False 227 | base_reloc.size_of_block = cls.sizeof + base_reloc.num_of_type_offsets * struct_size 228 | base_reloc.type_offsets = type_offsets 229 | return base_reloc 230 | 231 | def __repr__(self) -> str: 232 | return f'Virtual Address: {hex(self.virtual_address)} | size of Block: {hex(self.size_of_block)} | ' \ 233 | f'Num of Type Offsets: {self.num_of_type_offsets} | ' \ 234 | f'Type Offsest: {["RVA " + hex(offset) + " Type " + str(offset_type) for offset, offset_type in self.type_offsets]}\n' 235 | 236 | def dump(self) -> bytearray: 237 | # empty bytes at the end for padding 238 | if self.is_word_sized: 239 | struct_type, struct_size = 'H', 2 240 | else: 241 | struct_type, struct_size = 'I', 4 242 | packed_base_relocation = bytearray(struct.pack(' 'IMAGE_FUNCTION_OVERRIDE_DYNAMIC_RELOCATION': 263 | """ 264 | Creates an IMAGE_FUNCTION_OVERRIDE_DYNAMIC_RELOCATION object from the raw data 265 | :param raw_data: raw data of the IMAGE_FUNCTION_OVERRIDE_DYNAMIC_RELOCATION 266 | :return: IMAGE_FUNCTION_OVERRIDE_DYNAMIC_RELOCATION object 267 | """ 268 | fo_dyn_reloc = cls() 269 | fo_dyn_reloc.original_rva, fo_dyn_reloc.bdd_offset, fo_dyn_reloc.rva_size, fo_dyn_reloc.base_reloc_size = \ 270 | struct.unpack(' 0: 277 | fo_dyn_reloc.rva_list = list(struct.unpack('<' + 'I' * (fo_dyn_reloc.rva_size // 4), rva_list_data)) 278 | 279 | idx = 0 280 | reloc_data = raw_data[fo_dyn_reloc.sizeof + fo_dyn_reloc.rva_size:] 281 | while idx <= fo_dyn_reloc.base_reloc_size - IMAGE_BASE_RELOCATION.sizeof: 282 | reloc = IMAGE_BASE_RELOCATION.from_bytes(reloc_data[idx:]) 283 | idx += reloc.size_of_block 284 | fo_dyn_reloc.base_relocations.append(reloc) 285 | 286 | return fo_dyn_reloc 287 | 288 | @classmethod 289 | def from_data(cls, original_rva: int, bdd_offset: int, rva_list: List[int], base_relocations: List[IMAGE_BASE_RELOCATION]) -> 'IMAGE_FUNCTION_OVERRIDE_DYNAMIC_RELOCATION': 290 | """ 291 | Creates an IMAGE_FUNCTION_OVERRIDE_DYNAMIC_RELOCATION object from the given data 292 | :param virtual_address: virtual address of the base relocation 293 | :param type_offsets: list of type offsets 294 | :return: IMAGE_FUNCTION_OVERRIDE_DYNAMIC_RELOCATION object 295 | """ 296 | fo_dyn_reloc = cls() 297 | fo_dyn_reloc.original_rva = original_rva 298 | fo_dyn_reloc.bdd_offset = bdd_offset 299 | fo_dyn_reloc.rva_list = rva_list 300 | fo_dyn_reloc.rva_size = len(rva_list) * 4 301 | fo_dyn_reloc.base_relocations = base_relocations 302 | fo_dyn_reloc.base_reloc_size = sum([reloc.size_of_block for reloc in base_relocations]) 303 | fo_dyn_reloc.total_size = fo_dyn_reloc.sizeof + fo_dyn_reloc.rva_size + fo_dyn_reloc.base_reloc_size 304 | return fo_dyn_reloc 305 | 306 | def __repr__(self) -> str: 307 | return f'Original RVA: {hex(self.original_rva)} | BDD Offset: {self.bdd_offset} | ' \ 308 | f'RVA Array Size: {self.rva_size} | Base Relocation Size: {self.base_reloc_size} | ' \ 309 | f'RVA Array: {self.rva_list}\n' \ 310 | f'Relocations Info: {self.base_relocations}' 311 | 312 | def dump(self) -> bytearray: 313 | data = bytearray(struct.pack(' 'IMAGE_FUNCTION_OVERRIDE_HEADER': 330 | """ 331 | Creates an IMAGE_FUNCTION_OVERRIDE_HEADER object from the raw data 332 | :param raw_data: raw data of the IMAGE_FUNCTION_OVERRIDE_HEADER 333 | :return: IMAGE_FUNCTION_OVERRIDE_HEADER object 334 | """ 335 | header = cls() 336 | header.func_override_size = struct.unpack(' 'IMAGE_FUNCTION_OVERRIDE_HEADER': 353 | """ 354 | Creates an IMAGE_FUNCTION_OVERRIDE_HEADER object from the given data 355 | :return: IMAGE_FUNCTION_OVERRIDE_HEADER object 356 | """ 357 | header = cls() 358 | header.func_override_size = sum([fo_reloc.total_size for fo_reloc in func_override_info]) 359 | header.func_override_info = func_override_info 360 | header.bdd_info = bdd_info 361 | return header 362 | 363 | def __repr__(self) -> str: 364 | return f'FunctionOverrideSize: {self.func_override_size}\n' \ 365 | f'Function Override Dynamic Relocations: {self.func_override_info}\n' \ 366 | f'BDD Info: {self.bdd_info}' 367 | 368 | def dump(self) -> bytearray: 369 | data = bytearray(struct.pack(' 'IMAGE_BDD_DYNAMIC_RELOCATION': 386 | """ 387 | Creates an IMAGE_BDD_DYNAMIC_RELOCATION object from the raw data 388 | :param raw_data: raw data of the IMAGE_BDD_DYNAMIC_RELOCATION 389 | :return: IMAGE_BDD_DYNAMIC_RELOCATION object 390 | """ 391 | bdd_dynamic_reloc = cls() 392 | bdd_dynamic_reloc.left, bdd_dynamic_reloc.right, bdd_dynamic_reloc.value = \ 393 | struct.unpack(' str: 407 | return f'Left: {self.left} | Right: {self.right} | ' \ 408 | f'Value: {hex(self.value)}' 409 | 410 | def dump(self) -> bytearray: 411 | return bytearray(struct.pack(' 'IMAGE_BDD_INFO': 424 | """ 425 | Creates an IMAGE_BDD_INFO object from the raw data 426 | :param raw_data: raw data of the IMAGE_BDD_INFO 427 | :return: IMAGE_BDD_INFO object 428 | """ 429 | bdd_info = cls() 430 | bdd_info.version, bdd_info.bdd_size = struct.unpack(' 'IMAGE_BDD_INFO': 442 | """ 443 | Creates an IMAGE_BDD_INFO object from the given data 444 | :return: IMAGE_BDD_INFO object 445 | """ 446 | bdd_info = cls() 447 | bdd_info.version = version 448 | bdd_info.bdd_size = len(bdd_nodes_list) * IMAGE_BDD_DYNAMIC_RELOCATION.sizeof 449 | bdd_info.bdd_nodes_list = bdd_nodes_list 450 | return bdd_info 451 | 452 | def __repr__(self) -> str: 453 | return f'Version: {self.version} | BDD Size: {self.bdd_size} \n' \ 454 | f'BDD Nodes: {self.bdd_nodes_list}' 455 | 456 | def dump(self) -> bytearray: 457 | data = bytearray(struct.pack(' 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 | Debug 22 | ARM 23 | 24 | 25 | Release 26 | ARM 27 | 28 | 29 | Debug 30 | ARM64 31 | 32 | 33 | Release 34 | ARM64 35 | 36 | 37 | 38 | {2B52103C-0BDD-445A-A274-1D91F7139477} 39 | {1bc93793-694f-48fe-9372-81e2b05556fd} 40 | v4.5 41 | 12.0 42 | Debug 43 | Win32 44 | helper 45 | 46 | 47 | 48 | Windows10 49 | true 50 | WindowsKernelModeDriver10.0 51 | Driver 52 | KMDF 53 | Universal 54 | 1 55 | 9 56 | 57 | 58 | Windows10 59 | false 60 | WindowsKernelModeDriver10.0 61 | Driver 62 | KMDF 63 | Universal 64 | 1 65 | 9 66 | 67 | 68 | Windows10 69 | true 70 | WindowsKernelModeDriver10.0 71 | Driver 72 | WDM 73 | Universal 74 | Spectre 75 | 76 | 77 | Windows10 78 | false 79 | WindowsKernelModeDriver10.0 80 | Driver 81 | WDM 82 | Universal 83 | Spectre 84 | 85 | 86 | Windows10 87 | true 88 | WindowsKernelModeDriver10.0 89 | Driver 90 | KMDF 91 | Universal 92 | 93 | 94 | Windows10 95 | false 96 | WindowsKernelModeDriver10.0 97 | Driver 98 | KMDF 99 | Universal 100 | 101 | 102 | Windows10 103 | true 104 | WindowsKernelModeDriver10.0 105 | Driver 106 | KMDF 107 | Universal 108 | 109 | 110 | Windows10 111 | false 112 | WindowsKernelModeDriver10.0 113 | Driver 114 | KMDF 115 | Universal 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | DbgengKernelDebugger 127 | true 128 | 129 | 130 | DbgengKernelDebugger 131 | true 132 | 133 | 134 | DbgengKernelDebugger 135 | 136 | 137 | DbgengKernelDebugger 138 | 139 | 140 | DbgengKernelDebugger 141 | 142 | 143 | DbgengKernelDebugger 144 | 145 | 146 | DbgengKernelDebugger 147 | 148 | 149 | DbgengKernelDebugger 150 | 151 | 152 | 153 | true 154 | 155 | 156 | 157 | 158 | MultiThreaded 159 | EnableAllWarnings 160 | false 161 | 162 | 163 | SHA256 164 | 165 | 166 | 167 | 168 | MultiThreaded 169 | false 170 | 171 | 172 | SHA256 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | -------------------------------------------------------------------------------- /helper/helper.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hpp;hxx;hm;inl;inc;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 | {8E41214B-6785-4CFE-B992-037D68949A14} 18 | inf;inv;inx;mof;mc; 19 | 20 | 21 | 22 | 23 | Driver Files 24 | 25 | 26 | 27 | 28 | Source Files 29 | 30 | 31 | 32 | 33 | Header Files 34 | 35 | 36 | -------------------------------------------------------------------------------- /helper/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "main.h" 5 | 6 | #pragma warning (disable: 4201) 7 | #define DRIVER_NAME L"\\Device\\helper" 8 | #define DEVICE_NAME L"\\DosDevices\\helper" 9 | #define MAX_PATH_LEN 1024 10 | 11 | 12 | 13 | 14 | #define IMAGE_SIZEOF_SHORT_NAME 8 15 | #define IMAGE_DIRECTORY_ENTRY_EXPORT 0 16 | #define IMAGE_NUMBEROF_DIRECTORY_ENTRIES 16 17 | 18 | typedef enum _SYSTEM_INFORMATION_CLASS { // indicates what information the ZwQuerySystemInformation function should query 19 | SystemBasicInformation, 20 | SystemProcessorInformation, 21 | SystemPerformanceInformation, 22 | SystemTimeOfDayInformation, 23 | SystemPathInformation, 24 | SystemProcessInformation, 25 | SystemCallCountInformation, 26 | SystemDeviceInformation, 27 | SystemProcessorPerformanceInformation, 28 | SystemFlagsInformation, 29 | SystemCallTimeInformation, 30 | SystemModuleInformation, // to get information about all kernel modules 31 | } SYSTEM_INFORMATION_CLASS, *PSYSTEM_INFORMATION_CLASS; 32 | 33 | // Class 11 34 | typedef struct _SYSTEM_MODULE_INFORMATION_ENTRY 35 | { 36 | ULONG Unknown1; 37 | ULONG Unknown2; 38 | #ifdef _WIN64 39 | ULONG Unknown3; 40 | ULONG Unknown4; 41 | #endif 42 | PVOID Base; 43 | ULONG Size; 44 | ULONG Flags; 45 | USHORT Index; 46 | USHORT NameLength; 47 | USHORT LoadCount; 48 | USHORT NameOffset; 49 | CHAR ImageName[256]; 50 | } SYSTEM_MODULE_INFORMATION_ENTRY, *PSYSTEM_MODULE_INFORMATION_ENTRY; 51 | 52 | typedef struct _SYSTEM_MODULE_INFORMATION 53 | { 54 | ULONG Count; 55 | SYSTEM_MODULE_INFORMATION_ENTRY Modules[1]; 56 | } SYSTEM_MODULE_INFORMATION, * PSYSTEM_MODULE_INFORMATION; 57 | 58 | //extern "C" 59 | NTKERNELAPI NTSTATUS ZwQuerySystemInformation(IN SYSTEM_INFORMATION_CLASS SystemInformationClass, IN OUT PVOID SystemInformation, IN ULONG SystemInformationLength, OUT PULONG ReturnLength OPTIONAL); 60 | 61 | /* 62 | * Function: KernelGetModuleBase 63 | * ----------------------------- 64 | * Finds the base address of a kernel module 65 | * 66 | * pModuleName: name of module to look for 67 | * 68 | * returns: module base address, NULL on error. 69 | */ 70 | PVOID KernelGetModuleBase(PCHAR pModuleName) 71 | { 72 | PVOID pModuleBase = NULL; 73 | PULONG pSystemInfoBuffer = NULL; 74 | NTSTATUS status = STATUS_INSUFFICIENT_RESOURCES; 75 | 76 | __try 77 | { 78 | ULONG SystemInfoBufferSize = 0; 79 | 80 | //first passing NULL to SystemInformation to get the accurate size of the information and allocate memory accordingly 81 | status = ZwQuerySystemInformation(SystemModuleInformation, 82 | NULL, 83 | 0, 84 | &SystemInfoBufferSize); 85 | 86 | if (NT_SUCCESS(status)) { 87 | return NULL; 88 | } 89 | 90 | if (!SystemInfoBufferSize) { 91 | return NULL; 92 | } 93 | 94 | KdPrint(("SystemInfoBufferSize: 0x%x", SystemInfoBufferSize)); 95 | pSystemInfoBuffer = (PULONG)ExAllocatePoolWithTag(NonPagedPool, SystemInfoBufferSize, 0x12345678); 96 | 97 | if (!pSystemInfoBuffer) { 98 | KdPrint(("pSystemInfoBuffer allocation failed.\n")); 99 | return NULL; 100 | } 101 | 102 | memset(pSystemInfoBuffer, 0, SystemInfoBufferSize); 103 | 104 | // calling the function to get information about all kernel modules 105 | status = ZwQuerySystemInformation(SystemModuleInformation, 106 | pSystemInfoBuffer, 107 | SystemInfoBufferSize, 108 | &SystemInfoBufferSize); 109 | 110 | if (NT_SUCCESS(status)) 111 | { 112 | PSYSTEM_MODULE_INFORMATION systemModulesInformation = (PSYSTEM_MODULE_INFORMATION)pSystemInfoBuffer; 113 | PSYSTEM_MODULE_INFORMATION_ENTRY pSysModuleEntry = systemModulesInformation->Modules; 114 | ULONG i; 115 | KdPrint(("systemModulesInformation Count: 0x%p\n", systemModulesInformation->Count)); 116 | 117 | //iterating over all systemModulesInformation entries, untill it reaches the one that matches the requested name 118 | for (i = 0; i < systemModulesInformation->Count; i++) 119 | { 120 | if (_stricmp(pSysModuleEntry[i].ImageName + 121 | pSysModuleEntry[i].NameOffset, pModuleName) == 0) 122 | { 123 | pModuleBase = pSysModuleEntry[i].Base; 124 | break; 125 | } 126 | } 127 | } 128 | 129 | } 130 | __except (EXCEPTION_EXECUTE_HANDLER) 131 | { 132 | pModuleBase = NULL; 133 | status = GetExceptionCode(); 134 | DbgPrint("Exception code: 0x%X\n", status); 135 | } 136 | if (pSystemInfoBuffer) { 137 | ExFreePool(pSystemInfoBuffer); 138 | } 139 | 140 | return pModuleBase; 141 | } // end KernelGetModuleBase() 142 | 143 | void Unload(PDRIVER_OBJECT pDrvObj) 144 | { 145 | UNICODE_STRING usSymboName; 146 | 147 | KdPrint(("helper driver unloaded !\n")); 148 | RtlInitUnicodeString(&usSymboName, DEVICE_NAME); 149 | IoDeleteSymbolicLink(&usSymboName); 150 | if (pDrvObj->DeviceObject != NULL) { 151 | IoDeleteDevice(pDrvObj->DeviceObject); 152 | } 153 | } 154 | 155 | NTSTATUS DispatchCreateClose(PDEVICE_OBJECT pDevObj, PIRP pIrp) 156 | { 157 | UNREFERENCED_PARAMETER(pDevObj); 158 | UNREFERENCED_PARAMETER(pIrp); 159 | 160 | pIrp->IoStatus.Information = 0; 161 | pIrp->IoStatus.Status = STATUS_SUCCESS; 162 | IoCompleteRequest(pIrp, IO_NO_INCREMENT); 163 | return STATUS_SUCCESS; 164 | } 165 | 166 | /* 167 | * Function: ReadOrWriteMemory 168 | * --------------------- 169 | * Writes / Reads data to memory 170 | * 171 | * ulAddr: Address to write to / read from 172 | * pData: pointer to data to write /pointer to buffer to read into 173 | * ulLen: length of data 174 | * read: TRUE for read action, FALSE for write action 175 | * 176 | * returns: Write operation status 177 | */ 178 | NTSTATUS ReadOrWriteMemory(ULONG_PTR ulAddr, UCHAR* pData, ULONG ulLen, BOOLEAN read) 179 | { 180 | PMDL ptrMdl = NULL; 181 | PVOID ptrBuffer = NULL; 182 | NTSTATUS status = STATUS_UNSUCCESSFUL; 183 | 184 | ptrMdl = IoAllocateMdl((PVOID)ulAddr, ulLen, FALSE, FALSE, NULL); 185 | if (ptrMdl == NULL) { 186 | KdPrint(("IoAllocateMdl failed\n")); 187 | return status; 188 | } 189 | else { 190 | __try { 191 | MmProbeAndLockPages(ptrMdl, KernelMode, IoModifyAccess); 192 | ptrBuffer = MmMapLockedPagesSpecifyCache(ptrMdl, KernelMode, MmCached, NULL, FALSE, HighPagePriority); 193 | if (ptrBuffer == NULL) { 194 | KdPrint(("MmMapLockedPagesSpecifyCache failed\n")); 195 | } 196 | else { 197 | status = MmProtectMdlSystemAddress(ptrMdl, PAGE_EXECUTE_READWRITE); 198 | if (status == STATUS_SUCCESS) { 199 | KdPrint(("MmProtectMdlSystemAddress successed\n")); 200 | if (read) { 201 | RtlCopyMemory(pData, ptrBuffer, ulLen); 202 | KdPrint(("Read data complete\n")); 203 | } 204 | else { 205 | RtlCopyMemory(ptrBuffer, pData, ulLen); 206 | KdPrint(("Write data complete\n")); 207 | } 208 | } 209 | else { 210 | KdPrint(("MmProtectMdlSystemAddress failed 0x%X\n", status)); 211 | } 212 | } 213 | } 214 | __except (EXCEPTION_EXECUTE_HANDLER) { 215 | status = GetExceptionCode(); 216 | DbgPrint("Exception code: 0x%X\n", status); 217 | } 218 | 219 | if (ptrBuffer) { 220 | MmUnmapLockedPages(ptrBuffer, ptrMdl); 221 | } 222 | 223 | if (ptrMdl) { 224 | MmUnlockPages(ptrMdl); 225 | IoFreeMdl(ptrMdl); 226 | } 227 | } 228 | return status; 229 | }// end ReadOrWriteMemory() 230 | 231 | 232 | /* 233 | * Function: WriteMemory 234 | * --------------------- 235 | * Writes data to memory 236 | * 237 | * ulAddr: Address to write to 238 | * pData: pointer to data 239 | * ulLen: length of data 240 | * 241 | * returns: Write operation status 242 | */ 243 | NTSTATUS WriteMemory(ULONG_PTR ulAddr, UCHAR* pData, ULONG ulLen) 244 | { 245 | return ReadOrWriteMemory(ulAddr, pData, ulLen, FALSE); 246 | }// end WriteMemory() 247 | 248 | /* 249 | * Function: ReadMemory 250 | * --------------------- 251 | * Reads data from memory 252 | * 253 | * ulAddr: Address to read from 254 | * pData: pointer to where to read into 255 | * ulLen: length of data to read 256 | * 257 | * returns: Read operation status 258 | */ 259 | NTSTATUS ReadMemory(ULONG_PTR ulAddr, UCHAR* pData, ULONG ulLen) 260 | { 261 | return ReadOrWriteMemory(ulAddr, pData, ulLen, TRUE); 262 | }// end ReadMemory() 263 | 264 | NTSTATUS MapMemory(ULONG_PTR ulAddr, ULONG ulLen, PVOID *ptrBuffer, PMDL *ptrMdl) 265 | { 266 | NTSTATUS status = STATUS_UNSUCCESSFUL; 267 | 268 | *ptrMdl = IoAllocateMdl((PVOID)ulAddr, ulLen, FALSE, FALSE, NULL); 269 | if (*ptrMdl == NULL) { 270 | KdPrint(("IoAllocateMdl failed\n")); 271 | return status; 272 | } 273 | else { 274 | __try { 275 | MmBuildMdlForNonPagedPool(*ptrMdl); 276 | *ptrBuffer = MmMapLockedPagesSpecifyCache(*ptrMdl, UserMode, MmCached, NULL, FALSE, HighPagePriority); 277 | if (*ptrBuffer == NULL) { 278 | KdPrint(("MmMapLockedPagesSpecifyCache failed\n")); 279 | } 280 | return STATUS_SUCCESS; 281 | } 282 | __except (EXCEPTION_EXECUTE_HANDLER) { 283 | status = GetExceptionCode(); 284 | DbgPrint("Exception code: 0x%X\n", status); 285 | } 286 | } 287 | return status; 288 | }// end MapMemory() 289 | 290 | VOID UnmapMemory(PVOID ptrBuffer, PMDL ptrMdl) 291 | { 292 | if (ptrBuffer) { 293 | MmUnmapLockedPages(ptrBuffer, ptrMdl); 294 | } 295 | if (ptrMdl) { 296 | MmUnlockPages(ptrMdl); 297 | IoFreeMdl(ptrMdl); 298 | } 299 | }// end UnmapMemory() 300 | 301 | #define DllExport _declspec(dllexport) 302 | DWORD tbl_size = 0x100; 303 | DllExport DWORD tbl_idx = 0; 304 | DllExport DWORD* tbl = NULL; 305 | // DllExport NTSTATUS callback() { 306 | // if (!tbl) tbl = ExAllocatePoolWithTag(NonPagedPoolNx, tbl_size * sizeof(DWORD), 0x41414141); 307 | // if (tbl_idx >= tbl_size) { 308 | // DWORD* old_tbl = tbl; 309 | // tbl_size *= 2; 310 | // tbl = ExAllocatePoolWithTag(NonPagedPoolNx, tbl_size * sizeof(DWORD), 0x41414141); 311 | // memcpy(tbl, old_tbl, tbl_size * sizeof(DWORD) / 2); 312 | // ExFreePoolWithTag(old_tbl, 0x41414141); 313 | // } 314 | // tbl[tbl_idx++] = (DWORD)_ReturnAddress(); 315 | // return 0; 316 | // } 317 | 318 | void dump_and_reset_callback() { 319 | for (DWORD i = 0; i < tbl_idx; i++) { 320 | KdPrint(("%x\n", tbl[i])); 321 | } 322 | if (tbl) { 323 | ExFreePoolWithTag(tbl, 0x41414141); 324 | tbl = NULL; 325 | tbl_size = 0x100; 326 | tbl_idx = 0; 327 | } 328 | } 329 | 330 | NTSTATUS DispatchIoctl(PDEVICE_OBJECT pDevObj, PIRP pIrp) 331 | { 332 | UNREFERENCED_PARAMETER(pDevObj); 333 | ULONG i = 0, code = 0, len = 0; 334 | DWORD32 size = 0; 335 | ULONG_PTR ptrBaseAddr = 0, ptrRetAddr = 0; 336 | DWORD32 ptrTag = 0, ptrType = 0; 337 | ULONG_PTR ptrUserAddr = 0, ptrMdl = 0; 338 | PIO_STACK_LOCATION stack = NULL; 339 | ANSI_STRING cov = { 4,5,".cov" }; 340 | ANSI_STRING secName; 341 | NTSTATUS status = STATUS_SUCCESS; 342 | 343 | stack = IoGetCurrentIrpStackLocation(pIrp); 344 | code = stack->Parameters.DeviceIoControl.IoControlCode; 345 | switch (code) { 346 | case IOCTL_HELPER_GET_SECTION_ADDRESS: 347 | KdPrint(("Get Section Address: %ws\n", (PWCHAR)pIrp->AssociatedIrp.SystemBuffer)); 348 | 349 | CHAR name[MAX_PATH_LEN] = { 0 }; 350 | status = RtlStringCchPrintfA(name, MAX_PATH_LEN, "%ws", pIrp->AssociatedIrp.SystemBuffer); 351 | if (!NT_SUCCESS(status)) { 352 | KdPrint(("Failed creating string with error: %d\n", status)); 353 | len = 0; 354 | break; 355 | } 356 | ptrBaseAddr = (ULONG_PTR)KernelGetModuleBase(name); 357 | if (ptrBaseAddr == NULL) { 358 | KdPrint(("Base Addr not found...")); 359 | break; 360 | } 361 | else { 362 | KdPrint(("KernelGetModuleBase(%s) = 0x%p\n", name, ptrBaseAddr)); 363 | 364 | if (ptrBaseAddr && (((PUCHAR)ptrBaseAddr)[0] == 'M') && (((PUCHAR)ptrBaseAddr)[1] == 'Z')) { 365 | IMAGE_DOS_HEADER *pDosHeader = (IMAGE_DOS_HEADER*)ptrBaseAddr; 366 | IMAGE_NT_HEADERS *pImageNTHeader = (IMAGE_NT_HEADERS*)((PUCHAR)ptrBaseAddr + pDosHeader->e_lfanew); 367 | IMAGE_FILE_HEADER *pFileheader = &pImageNTHeader->FileHeader; 368 | IMAGE_OPTIONAL_HEADER *pImageOptionalHeader = &pImageNTHeader->OptionalHeader; 369 | IMAGE_SECTION_HEADER *sectionHeader = (IMAGE_SECTION_HEADER*)((PUCHAR)pImageOptionalHeader + pFileheader->SizeOfOptionalHeader); 370 | for (i = 0; iNumberOfSections; i++) { 371 | RtlInitAnsiString(&secName, (PCSZ)sectionHeader->Name); 372 | if (RtlCompareString(&secName, &cov, TRUE) == 0) { 373 | ptrRetAddr = sectionHeader->VirtualAddress; 374 | } 375 | /* 376 | KdPrint(("------------------------------------------------------------------\n")); 377 | KdPrint(("Section Name = %s\n", sectionHeader->Name)); 378 | KdPrint(("Virtual Offset = %X\n", sectionHeader->VirtualAddress)); 379 | KdPrint(("Virtual Size = %X\n", sectionHeader->Misc.VirtualSize)); 380 | KdPrint(("Raw Offset = %X\n", sectionHeader->PointerToRawData)); 381 | KdPrint(("Raw Size = %X\n", sectionHeader->SizeOfRawData)); 382 | KdPrint(("Characteristics = %X\n", sectionHeader->Characteristics)); 383 | KdPrint(("------------------------------------------------------------------\n")); 384 | */ 385 | sectionHeader++; 386 | } 387 | } 388 | if(ptrRetAddr) ptrRetAddr += ptrBaseAddr; 389 | KdPrint((".cov Address: 0x%p\n", ptrRetAddr)); 390 | *((ULONG_PTR*)pIrp->AssociatedIrp.SystemBuffer) = ptrRetAddr; 391 | 392 | len = sizeof(ULONG_PTR); 393 | break; 394 | } 395 | 396 | case IOCTL_HELPER_READ_MEMORY: 397 | ptrBaseAddr = *(ULONG_PTR*)pIrp->AssociatedIrp.SystemBuffer; 398 | KdPrint(("Read memory: addr 0x%llX, size %d\n", ptrBaseAddr, stack->Parameters.DeviceIoControl.InputBufferLength)); 399 | ReadMemory(ptrBaseAddr, (UCHAR*)pIrp->AssociatedIrp.SystemBuffer, stack->Parameters.DeviceIoControl.InputBufferLength); 400 | len = stack->Parameters.DeviceIoControl.InputBufferLength; 401 | break; 402 | 403 | case IOCTL_HELPER_WRITE_MEMORY: 404 | ptrBaseAddr = *(ULONG_PTR*)pIrp->AssociatedIrp.SystemBuffer; 405 | KdPrint(("Write memory: addr 0x%X, size %d\n", ptrBaseAddr, stack->Parameters.DeviceIoControl.InputBufferLength)); 406 | WriteMemory(ptrBaseAddr, (UCHAR*)pIrp->UserBuffer, stack->Parameters.DeviceIoControl.InputBufferLength); 407 | len = 0; 408 | break; 409 | 410 | case IOCTL_HELPER_ALLOCATE_MEMORY: 411 | ptrType = *(DWORD32*)pIrp->AssociatedIrp.SystemBuffer; 412 | size = *((DWORD32*)pIrp->AssociatedIrp.SystemBuffer + 1); 413 | ptrTag = *((DWORD32*)pIrp->AssociatedIrp.SystemBuffer + 2); 414 | KdPrint(("Allocate memory: pooltype 0x%x, tag %X, size 0x%x\n", ptrType, ptrTag, size)); 415 | *((ULONG_PTR*)pIrp->AssociatedIrp.SystemBuffer) = (ULONG_PTR)ExAllocatePoolWithTag(ptrType, size, ptrTag); 416 | len = sizeof(ULONG_PTR); 417 | break; 418 | 419 | case IOCTL_HELPER_FREE_MEMORY: 420 | ptrBaseAddr = *(ULONG_PTR*)pIrp->AssociatedIrp.SystemBuffer; 421 | ptrTag = *(DWORD32*)((ULONG_PTR)pIrp->AssociatedIrp.SystemBuffer + sizeof(ULONG_PTR)); 422 | KdPrint(("free memory: tag %X, address 0x%x\n", ptrTag, ptrBaseAddr)); 423 | ExFreePoolWithTag((PVOID)ptrBaseAddr, ptrTag); 424 | len = 0; 425 | break; 426 | 427 | case IOCTL_HELPER_MAP_MEMORY: 428 | ptrBaseAddr = *(ULONG_PTR*)pIrp->AssociatedIrp.SystemBuffer; 429 | size = *(DWORD32*)((ULONG_PTR)pIrp->AssociatedIrp.SystemBuffer + sizeof(ULONG_PTR)); 430 | KdPrint(("Kernel Address: 0x%p\n", ptrBaseAddr)); 431 | KdPrint(("Size: 0x%X\n", size)); 432 | NTSTATUS ns = MapMemory(ptrBaseAddr, size, (PVOID*)&ptrUserAddr, (PMDL*)&ptrMdl); 433 | if (ns == STATUS_SUCCESS) { 434 | KdPrint(("Mapped User Address: 0x%X\n", ptrUserAddr)); 435 | KdPrint(("MDL Address: 0x%X\n", ptrMdl)); 436 | } 437 | *((ULONG_PTR*)pIrp->AssociatedIrp.SystemBuffer) = ptrUserAddr; 438 | *((ULONG_PTR*)pIrp->AssociatedIrp.SystemBuffer + 1) = ptrMdl; 439 | len = sizeof(ULONG_PTR)*2; 440 | break; 441 | 442 | case IOCTL_HELPER_UNMAP_MEMORY: 443 | ptrUserAddr = *(ULONG_PTR*)pIrp->AssociatedIrp.SystemBuffer; 444 | ptrMdl = *((ULONG_PTR*)pIrp->AssociatedIrp.SystemBuffer + 1); 445 | KdPrint(("Unmapped User Address: 0x%X\n", ptrUserAddr)); 446 | KdPrint(("MDL Address: 0x%X\n", ptrMdl)); 447 | UnmapMemory((PVOID)ptrUserAddr, (PMDL)ptrMdl); 448 | len = 0; 449 | break; 450 | 451 | case IOCTL_HELPER_DUMP_AND_RESET_CALLBACK: 452 | dump_and_reset_callback(); 453 | len = 0; 454 | break; 455 | 456 | default: 457 | KdPrint(("Invalid IOCTL code: 0x%X\n", code)); 458 | break; 459 | } 460 | 461 | pIrp->IoStatus.Information = len; 462 | pIrp->IoStatus.Status = STATUS_SUCCESS; 463 | IoCompleteRequest(pIrp, IO_NO_INCREMENT); 464 | return STATUS_SUCCESS; 465 | } 466 | 467 | NTSTATUS DriverEntry(PDRIVER_OBJECT pDrvObj, PUNICODE_STRING pRegPath) 468 | { 469 | PDEVICE_OBJECT pFunObj = NULL; 470 | UNICODE_STRING usDeviceName; 471 | UNICODE_STRING usSymboName; 472 | UNREFERENCED_PARAMETER(pRegPath); 473 | 474 | KdPrint(("helper driver loaded\n")); 475 | RtlInitUnicodeString(&usDeviceName, DRIVER_NAME); 476 | IoCreateDevice(pDrvObj, 0, &usDeviceName, FILE_DEVICE_UNKNOWN, 0, FALSE, &pFunObj); 477 | RtlInitUnicodeString(&usSymboName, DEVICE_NAME); 478 | IoCreateSymbolicLink(&usSymboName, &usDeviceName); 479 | 480 | pDrvObj->MajorFunction[IRP_MJ_CREATE] = 481 | pDrvObj->MajorFunction[IRP_MJ_CLOSE] = DispatchCreateClose; 482 | pDrvObj->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DispatchIoctl; 483 | pDrvObj->DriverUnload = Unload; 484 | return STATUS_SUCCESS; 485 | } 486 | -------------------------------------------------------------------------------- /helper/main.h: -------------------------------------------------------------------------------- 1 | #ifndef __MAIN_H__ 2 | #define __MAIN_H__ 3 | 4 | #define IOCTL_HELPER_GET_SECTION_ADDRESS CTL_CODE(FILE_DEVICE_UNKNOWN, 801, METHOD_BUFFERED, FILE_ANY_ACCESS) //0x220c84 5 | #define IOCTL_HELPER_READ_MEMORY CTL_CODE(FILE_DEVICE_UNKNOWN, 802, METHOD_BUFFERED, FILE_ANY_ACCESS) //0x220c88 6 | #define IOCTL_HELPER_WRITE_MEMORY CTL_CODE(FILE_DEVICE_UNKNOWN, 803, METHOD_BUFFERED, FILE_ANY_ACCESS) 7 | #define IOCTL_HELPER_ALLOCATE_MEMORY CTL_CODE(FILE_DEVICE_UNKNOWN, 804, METHOD_BUFFERED, FILE_ANY_ACCESS) 8 | #define IOCTL_HELPER_FREE_MEMORY CTL_CODE(FILE_DEVICE_UNKNOWN, 805, METHOD_BUFFERED, FILE_ANY_ACCESS) 9 | #define IOCTL_HELPER_MAP_MEMORY CTL_CODE(FILE_DEVICE_UNKNOWN, 806, METHOD_BUFFERED, FILE_ANY_ACCESS) 10 | #define IOCTL_HELPER_UNMAP_MEMORY CTL_CODE(FILE_DEVICE_UNKNOWN, 807, METHOD_BUFFERED, FILE_ANY_ACCESS) 11 | #define IOCTL_HELPER_DUMP_AND_RESET_CALLBACK CTL_CODE(FILE_DEVICE_UNKNOWN, 808, METHOD_BUFFERED, FILE_ANY_ACCESS) 12 | 13 | #endif 14 | -------------------------------------------------------------------------------- /ida_dumper.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2022 Gal Kristal, Dina Teper 3 | # Copyright (C) 2022 SentinelOne, Inc. 4 | # 5 | # SPDX-License-Identifier: AGPL-3.0-or-later 6 | 7 | import json 8 | import re 9 | 10 | import idaapi 11 | from typing import List, Dict 12 | from capstone import CS_ARCH_X86, CS_MODE_64, Cs 13 | from capstone.x86 import X86_OP_MEM, X86_REG_RIP 14 | from idautils import ida_bytes, Functions, Heads, Segments 15 | import idc 16 | from idc import set_color, CIC_ITEM 17 | 18 | ## Globals 19 | g_basic_blocks: List = [] 20 | g_relative_instructions: Dict = {} 21 | g_rip_relative_instructions: Dict = {} 22 | g_idata_section_info: List = [] 23 | g_jmp_tbls: Dict = {} 24 | f_c_handler, f_cpp_handler, f_gshandlercheck_seh = None, None, None 25 | g_func_addrs: Dict = {} 26 | g_possible_code: List = [] 27 | 28 | # Global disassembler object 29 | g_disassembler = Cs(CS_ARCH_X86, CS_MODE_64) 30 | g_disassembler.detail = True 31 | 32 | ## lambdas ## 33 | # Byte array to hex repr in python2 34 | ba2hex = lambda ba: "".join("%02x" % b for b in ba) 35 | 36 | ## Constants 37 | BASIC_BLOCK_COLOR = 0x6699ff 38 | EXPLICITLY_INCLUDED_COLOR = 0xffffff 39 | EXPLICITLY_EXCLUDED_COLOR = 0x0 40 | 41 | 42 | def parse_relative(ea): 43 | """ 44 | Identify if the asm code at the given address contains a relative command 45 | :param ea: Address to test 46 | :return: (command in hex bytes, offset in hex bytes) 47 | """ 48 | buf = idc.get_bytes(ea, idc.get_item_size(ea)) 49 | idx = 0 50 | mpx_candidate = False 51 | 52 | # call (e8), http://x86.renejeschke.de/html/file_module_x86_id_26.html 53 | # jmp (eb/e9), http://x86.renejeschke.de/html/file_module_x86_id_147.html 54 | # jxx (0F 80/0F 81/0F 82/0F 83/0F 84/0F 85/0F 86/0F 87/0F 88/0F 89/0F 8A/0F 8B/0F 8C/0F 8D/0F 8E/0F 8F/70/71/72/73/74/75/76/77/78/79/7A/7B/7C/7D/7E/7F), http://x86.renejeschke.de/html/file_module_x86_id_146.html 55 | # jcxz/jecxz (67 e3/e3) 56 | # loop/loope/loopz/loopne/loopnz (e0/e1/e2), http://x86.renejeschke.de/html/file_module_x86_id_161.html 57 | if buf[idx] == 0xf2: 58 | idx += 1 59 | mpx_candidate = True 60 | 61 | if buf[idx] in {0xe0, 0xe1, 0xe2, 0xe3, 0xe8, 0xe9, 0xeb}: 62 | idx += 1 63 | elif buf[idx] == 0x0f and (0x80 <= buf[idx + 1] <= 0x8f): 64 | idx += 2 65 | elif 0x70 <= buf[idx] <= 0x7f: 66 | idx += 1 67 | elif buf[idx] == 0x67 and buf[idx + 1] == 0xe3: 68 | idx += 2 69 | 70 | if mpx_candidate and idx == 1: 71 | idx = 0 72 | 73 | if idx: 74 | return buf[0:idx], buf[idx:] 75 | else: 76 | return None, None 77 | 78 | 79 | def add_relative_instruction(ea): 80 | """ 81 | Identify if the asm code at the given address contains a relative command. 82 | If it is, add it to the global dict of addresses of relative commands 83 | :param ea: Address to test 84 | :return: None 85 | """ 86 | global g_relative_instructions 87 | # need operand length, so parse it manually 88 | instruction_bytes, operand = parse_relative(ea) 89 | if instruction_bytes and operand: 90 | assert len(idc.print_operand(ea, 1)) == 0, 'more than 1 operand' 91 | assert len(operand) == 1 or len(operand) == 4, 'operand is not rel32' 92 | g_relative_instructions[ea] = [idc.get_operand_value(ea, 0), instruction_bytes.hex(), len(operand), 93 | len(instruction_bytes + operand)] 94 | return True 95 | return False 96 | 97 | 98 | def add_rip_relative_inst(ea): 99 | """ 100 | If the instruction at the given address is x64 rip-relative one, adds it to the "relative" dict 101 | :param ea: Address of instruction to test 102 | """ 103 | global g_relative_instructions 104 | # Go through all the instructions parsed (should be only one in this case) 105 | buf = idc.get_bytes(ea, idc.get_item_size(ea)) 106 | res = False 107 | for ins in g_disassembler.disasm(buf, 0): 108 | for op in ins.operands: 109 | if op.type == X86_OP_MEM and op.value.mem.base == X86_REG_RIP: 110 | res = True 111 | g_relative_instructions[ea] = [ins.disp + ea + len(ins.bytes), ba2hex(ins.bytes), ins.disp_size, 112 | len(ins.bytes)] 113 | return res 114 | 115 | 116 | def add_basic_block(ea, op): 117 | """ 118 | Identify if the asm code at the given address is the start of a basic block. 119 | Basic block for us is a conditional jump/call/loop. 120 | :param ea: Address to test 121 | :return: None 122 | """ 123 | global g_basic_blocks 124 | 125 | # validating branch, ie. jmp near ptr get_wide_dword_1007F84+1 126 | operand = idc.get_operand_value(ea, 0) 127 | if idc.prev_head(idc.next_head(operand)) != operand and idc.get_operand_type(ea, 0) in [idc.o_imm, idc.o_far, 128 | idc.o_near]: 129 | return 130 | 131 | # skip non-conditional branch 132 | if op in ('call', 'jmp'): 133 | return 134 | 135 | # identify as basic block, jxx/loop true/false target 136 | g_basic_blocks.append(idc.next_head(ea)) 137 | g_basic_blocks.append(operand) 138 | 139 | 140 | def set_basic_block_colors(): 141 | """ 142 | Helper function to color the start of every basic block we identified 143 | :return: None 144 | """ 145 | global g_basic_blocks 146 | for ea in g_basic_blocks: 147 | set_color(ea, CIC_ITEM, BASIC_BLOCK_COLOR) 148 | 149 | 150 | def check_unicode(ea): 151 | """ 152 | Check if an address is of a unicode/wide string. 153 | :param ea: Address 154 | :return: None 155 | """ 156 | if idc.get_type(ea) in ('const WCHAR', 'WCHAR', 'wchar_t'): 157 | ida_bytes.create_strlit(ea, 0, idc.STRTYPE_C_16) 158 | idc.auto_wait() 159 | if idc.get_str_type(ea) and idc.get_str_type(ea) & 0xFF != idc.STRTYPE_C_16 and idc.get_wide_word(ea) != 0: 160 | print('[WARN] Possible unicode @', ea) 161 | 162 | 163 | def check_suspicious_data(segea): 164 | """ 165 | Search the data of a segment for executable code instructions. 166 | Could be useful in case IDA missed it. 167 | :param segea: Segment's start address 168 | :return: 169 | """ 170 | global g_possible_code 171 | func_end_addrs = [idc.find_func_end(funcea) for funcea in Functions(segea, idc.get_segm_end(segea))] 172 | idc_get_wide_dword = idc.get_wide_dword 173 | idc_get_wide_byte = idc.get_wide_byte 174 | for idx, func_end_addr in enumerate(func_end_addrs): 175 | ofs = func_end_addr 176 | while idc_get_wide_byte(ofs) == 0xCC or idc_get_wide_byte(ofs) == 0x90: 177 | ofs += 1 178 | if idc.get_wide_word(ofs) == 0xff8b: # mov edi, edi 179 | idc.create_insn(ofs) 180 | continue 181 | for addr in range(ofs, ofs + 0x80): 182 | addr_wide_byte = idc_get_wide_byte(addr) 183 | addr_wide_dword = idc_get_wide_dword(addr) 184 | addr_disasm = idc.GetDisasm(addr) 185 | if (idc.is_code(idc.get_full_flags(addr)) or 186 | (idc.get_str_type(addr) is not None) or # string 187 | (idc.get_type(addr) is not None) or # struct 188 | ('offset' in addr_disasm or 'rva' in addr_disasm) or # valid data 189 | ('.' in addr_disasm) or # floating point 190 | (addr_wide_dword == 0xfffffffe or addr_wide_dword == 0xFFFFFFE4) or # GSCookieOffset 191 | ((addr_wide_dword >> 8) == 0) or # integer 192 | ('align' in addr_disasm) # alignment 193 | ): 194 | break 195 | if (addr_wide_byte in [0xe0, 0xe1, 0xe2, 0xe3, 0xe8, 0xe9, 0xeb] or # search for branch 196 | (0x70 <= addr_wide_byte <= 0x7f) or 197 | (addr_wide_byte == 0x67 and idc_get_wide_byte(addr + 1) == 0xe3) or 198 | (addr_wide_byte == 0x0f and (0x80 <= idc_get_wide_byte(addr + 1) <= 0x8f))): 199 | g_possible_code.append(addr) 200 | break 201 | idc.auto_wait() 202 | 203 | 204 | def calculate_jumptable_size(ea: int, parsed_size: int) -> int: 205 | """ 206 | Uses a heuristic to calculate the number of cases in a jumptable. 207 | This is relevant in cases where IDA miscalculates. 208 | @param ea: Address of the jumptable 209 | @param parsed_size: The size of the jumptable according to IDA 210 | @return: The number of cases in a jumptable. 211 | """ 212 | element_num = parsed_size 213 | ## Jumptable heuristics 214 | # Before the switch jump, there's a check that the jump is within bounds 215 | # For example, a switch-case of 5 cases, will have 'cmp eax, 4; ja label_default' 216 | # We're searching for that comparison. 217 | # If the jumptable uses an additional indirect table then we discard our previous check and trust IDA's parsing. 218 | # TODO Calculate the number of elements more precisely 219 | inc_up = ('jae', 'jnb', 'jnc') 220 | inc_down = ('jbe', 'jna') 221 | non_inc_up = ('ja', 'jnbe') 222 | non_inc_down = ('jb', 'jnae', 'jc') 223 | 224 | MAX_STEPS_BACK = 10 225 | prev_insn = idc.prev_head(ea) 226 | heur_element_num = 0 227 | found_indirect_table = False 228 | for i in range(MAX_STEPS_BACK): 229 | if idc.print_insn_mnem(prev_insn) == 'cmp': 230 | heur_element_num = idc.get_operand_value(prev_insn, 1) + 1 231 | break 232 | # This is indicative of an additional indirect table usage 233 | elif idc.print_insn_mnem(prev_insn) == 'movzx' and idc.print_operand(prev_insn, 0).endswith(('ax', 'cx', 'dx')): 234 | found_indirect_table = True 235 | prev_insn = idc.prev_head(prev_insn) 236 | if found_indirect_table == False and heur_element_num > element_num: 237 | print(f"At {hex(ea)}: Jumptable heuristic was used, parsed size: {element_num}, " 238 | f"heur size: {heur_element_num} (Found indirect: {found_indirect_table})") 239 | element_num = heur_element_num 240 | return element_num 241 | 242 | def check_jump_table(ea: int) -> None: 243 | """ 244 | Jump tables use hardcoded offsets that needs to be adjusted too. 245 | Fortunately, IDA recognizes and parses them pretty well 246 | :param ea: The address of the jmp table 247 | """ 248 | switch_info = idaapi.get_switch_info(ea) 249 | if not switch_info or switch_info.jumps == 0: 250 | return 251 | 252 | global g_jmp_tbls, g_basic_blocks 253 | func_dict = {1: ida_bytes.get_byte, 2: ida_bytes.get_16bit, 4: ida_bytes.get_wide_dword} 254 | loc = switch_info.jumps 255 | element_num = calculate_jumptable_size(ea, switch_info.get_jtable_size()) 256 | element_size = switch_info.get_jtable_element_size() 257 | elbase = switch_info.elbase 258 | if element_size == 4: 259 | for num in range(0, element_num): 260 | table_entry = loc + num * element_size 261 | if func_dict[element_size](table_entry) == 0: 262 | print(f"At {hex(ea)}: found empty entry (idx {num})") 263 | continue 264 | jmp_target = func_dict[element_size](table_entry) + elbase 265 | if not g_jmp_tbls.get(jmp_target): 266 | g_jmp_tbls[jmp_target] = [] 267 | g_jmp_tbls[jmp_target].append((table_entry, element_size, elbase)) 268 | g_basic_blocks.append(jmp_target) 269 | 270 | 271 | def identify_seh_handlers(): 272 | """ 273 | This is a best-effort code to identify common default exception handler functions, 274 | to use later in instrumentation when we patch the exception records for x64 binaries. 275 | """ 276 | global f_c_handler, f_cpp_handler, f_gshandlercheck_seh 277 | for func_addr in Functions(): 278 | func_name = idc.get_func_name(func_addr) 279 | if func_name == '__C_specific_handler': 280 | f_c_handler = func_addr 281 | elif func_name == '__GSHandlerCheck': 282 | f_cpp_handler = func_addr 283 | elif func_name == '__GSHandlerCheck_SEH': 284 | f_gshandlercheck_seh = func_addr 285 | 286 | 287 | def output_to_file(): 288 | """ 289 | Gather all collected data into a dict and dump it into a json file. 290 | :return: 291 | """ 292 | ida_dump = {'bb': g_basic_blocks, 'relative': g_relative_instructions, 'rip_inst': g_rip_relative_instructions, 293 | 'idata': g_idata_section_info, 'code_loc': g_func_addrs, 294 | 'jmp_tbls': g_jmp_tbls, 'c_handler': f_c_handler, 'cpp_handler': f_cpp_handler, 295 | 'gshandlercheck_seh': f_gshandlercheck_seh} 296 | print('[INFO]', str(len(g_basic_blocks)), 'blocks') 297 | print('[INFO]', str(len(g_relative_instructions)), 'branches') 298 | print('[INFO]', idc.get_input_file_path() + '.dump.json is created') 299 | with open(idc.get_input_file_path() + '.dump.json', 'w+') as f: 300 | json.dump(ida_dump, f) 301 | 302 | 303 | def partial_exclude(start, end=None): 304 | """ 305 | Exclude functions by offsets from the list of basic blocks we instrument. 306 | Examples: partial_exclude(ScreenEA()), partial_exclude(0x401020, 0x401040) 307 | :param start: Functions' start address 308 | :param end: Functions' end address 309 | :return: None 310 | """ 311 | global g_basic_blocks 312 | if end is None: 313 | # clear whole function 314 | start = idc.get_next_func(idc.get_prev_func(start)) 315 | end = idc.find_func_end(start) 316 | for head in Heads(start, end): 317 | if head in g_basic_blocks: 318 | set_color(head, CIC_ITEM, EXPLICITLY_EXCLUDED_COLOR) 319 | g_basic_blocks.remove(head) 320 | 321 | 322 | def partial_exclude_by_name(expr): 323 | """ 324 | Exclude functions by regex from the list of basic blocks we instrument. 325 | Example: partial_exclude_by_name('(_?Cm|_Hv[^il])') 326 | :param expr: regex of function names 327 | :return: None 328 | """ 329 | global g_basic_blocks 330 | func_finder = lambda x: re.search(expr, idc.get_func_name(x)) 331 | funcs_to_exclude = set(filter(func_finder, g_basic_blocks)) 332 | for func in funcs_to_exclude: 333 | set_color(func, CIC_ITEM, EXPLICITLY_EXCLUDED_COLOR) 334 | g_basic_blocks = list(set(g_basic_blocks) - funcs_to_exclude) 335 | 336 | 337 | def partial_include_by_name(expr): 338 | """ 339 | Include only functions that match the given regex in the list of basic blocks we instrument. 340 | Example: partial_include_by_name('(_?Cm|_Hv[^il])') 341 | :param expr: regex of function names 342 | :return: None 343 | """ 344 | global g_basic_blocks 345 | func_finder = lambda x: re.search(expr, idc.get_func_name(x)) 346 | funcs_to_include = set(filter(func_finder, g_basic_blocks)) 347 | for func in set(g_basic_blocks) - funcs_to_include: 348 | set_color(func, CIC_ITEM, EXPLICITLY_INCLUDED_COLOR) 349 | g_basic_blocks = list(funcs_to_include) 350 | 351 | 352 | def process_segment(segment_start, segment_end): 353 | """ 354 | Inspects each command in a segment for relevant things, such as basic blocks and relative commands. 355 | :param segment_start: Segment start address 356 | :param segment_end: Segment end address 357 | :return: None 358 | """ 359 | global g_func_addrs 360 | # check_suspicious_data(segment_start) # Currently commented out because it looks unnecessary, will be verified later 361 | func_start = None 362 | # This goes through each instruction or data item in the segment 363 | for addr in Heads(segment_start, idc.get_segm_end(segment_end)): 364 | # check_unicode(addr) # Currently commented out because it looks unnecessary, will be verified later 365 | if idc.is_code(idc.get_full_flags(addr)): 366 | if not func_start: 367 | func_start = addr 368 | # TODO: we parse the instruction both in add_relative_instruction and in add_rip_relative, can probably be optimized 369 | # these flags are for optimization and are meant to avoid unnecessary func calls. 370 | # If an instruction is relative, it cannot be rip relative. If an instruction is rip relative it cannot be a jump table. 371 | is_rip_rel = False 372 | is_rel = False 373 | op = idc.print_insn_mnem(addr) 374 | if op.startswith(('call', 'j', 'loop')): 375 | add_basic_block(addr, op) 376 | is_rel = add_relative_instruction(addr) 377 | if not is_rel: 378 | is_rip_rel = add_rip_relative_inst(addr) 379 | if not is_rip_rel: 380 | check_jump_table(addr) 381 | else: 382 | if func_start is not None: 383 | g_func_addrs[func_start] = addr 384 | func_start = None 385 | 386 | 387 | def process_file(): 388 | """ 389 | The main function of this script. This parses the PE and outputs it to a file. 390 | :return: 391 | """ 392 | global g_basic_blocks, g_idata_section_info 393 | idc.auto_wait() 394 | g_idata_section_info = [[x, idc.get_segm_end(x)] for x in Segments() if 395 | (idaapi.getseg(x).perm & idaapi.SEGPERM_EXEC) and idc.get_segm_name(x) == '.idata'] 396 | segments = [[x, idc.get_segm_end(x)] for x in Segments() if 397 | (idaapi.getseg(x).perm & idaapi.SEGPERM_EXEC) and idc.get_segm_name(x) != '.idata'] 398 | for segment_start, segment_end in segments: 399 | process_segment(segment_start, segment_end) 400 | 401 | g_basic_blocks = sorted(list(set(g_basic_blocks))) 402 | set_basic_block_colors() 403 | identify_seh_handlers() 404 | 405 | # dump result 406 | print( 407 | '[INFO] To do partial instrumentation use the functions partial_exclude/partial_exclude_by_name/partial_include_by_name') 408 | print('[INFO] And then call output_to_file() again') 409 | output_to_file() 410 | 411 | 412 | if __name__ == '__main__': 413 | process_file() 414 | -------------------------------------------------------------------------------- /pe_afl.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2022 Gal Kristal, Dina Teper 3 | # Copyright (C) 2022 SentinelOne, Inc. 4 | # 5 | # SPDX-License-Identifier: AGPL-3.0-or-later 6 | 7 | import argparse 8 | import functools 9 | import logging 10 | import re 11 | import pefile 12 | from typing import Optional, Tuple 13 | 14 | import instrument 15 | from asm_stubs64 import (asm, extract_exported_function_bytes, C_ADDR1, C_ADDR2, MAGIC, sc_suffix, sc_prefix, asm_nop, 16 | asm_single, asm_filter, asm_callback, asm_thread_filter_init, asm_thread_filter_generic) 17 | from utils import pack_64bit, is_pe_32bit, is_driver 18 | 19 | LOGGER_FORMAT_NORMAL = '[*] %(message)s' 20 | LOGGER_FORMAT_VERBOSE = '[*] %(asctime)s - %(levelname)s - %(message)s' 21 | 22 | 23 | def init_shellcodes(args, nt_path: Optional[str]) -> Tuple[bytearray, Optional[bytearray]]: 24 | """ 25 | Initializes the different instrumentation shellcodes. 26 | :param args: runtime arguments 27 | :param nt_path: ntoskrnl path, for driver instrumentation 28 | :return: Tuple of instrumentation shellcodes 29 | """ 30 | getpid_func_bytes = b'' 31 | gettid_func_bytes = b'' 32 | if nt_path: 33 | exporting_pe = pefile.PE(nt_path) 34 | getpid_func_bytes = extract_exported_function_bytes(exporting_pe, b'PsGetCurrentProcessId') 35 | gettid_func_bytes = extract_exported_function_bytes(exporting_pe, b'PsGetCurrentThreadId') 36 | if args.nop: 37 | return asm_nop, None 38 | elif args.callback: 39 | return asm(sc_prefix) + getpid_func_bytes + asm(f"{asm_callback} {sc_suffix}"), None 40 | elif args.filter: 41 | return asm(sc_prefix) + getpid_func_bytes + asm(f"{asm_filter} {sc_suffix}"), None 42 | elif args.thread_filter: 43 | return asm(sc_prefix) + gettid_func_bytes + asm(f"{asm_thread_filter_generic} {sc_suffix}"), \ 44 | asm(sc_prefix) + gettid_func_bytes + asm(f"{asm_thread_filter_init} {sc_suffix}") 45 | else: 46 | # The most basic instrumentation 47 | return asm(f"{sc_prefix} {asm_single} {sc_suffix}"), None 48 | 49 | 50 | def update_snippet_with_addr(addr: int, shellcode: bytes) -> bytes: 51 | """ 52 | replaces the snippet's address magic with the address 53 | :param addr: The address of the instrumentation 54 | :param shellcode: The instrumentation shellcode 55 | :return: instrumentation shellcode with the updated address 56 | """ 57 | r = shellcode.replace(pack_64bit(C_ADDR1), pack_64bit(addr & 0xFFFF), 1) 58 | return r.replace(pack_64bit(C_ADDR2), pack_64bit((addr >> 1) & 0xFFFF)) 59 | 60 | 61 | def parse_arguments(): 62 | parser = argparse.ArgumentParser() 63 | parser.add_argument('-n', '--nop', help='Instrument with NOPs for testing', action='store_true') 64 | parser.add_argument('-cb', '--callback', 65 | help='Instrument with a callback, which is in the helper driver that\'s written in C', 66 | action='store_true') 67 | parser.add_argument('-tf', '--thread-filter', 68 | help='Driver instrumentation that filters only on thread ID (must use "-te" with this option)', 69 | action='store_true') 70 | parser.add_argument('-te', '--thread-entry', help='The address (RVA) of the thread\'s initialization function', 71 | type=functools.partial(int, base=0)) 72 | parser.add_argument('-nt', '--ntoskrnl', 73 | help='ntoskrnl.exe path for offset extraction (non-optional if instrumenting a driver)') 74 | parser.add_argument('-e', '--entry', help='Inject code on entry point, ie. -e9090') 75 | parser.add_argument('-l', '--enlarge', help='Enlarge factor for sections, default=4') 76 | parser.add_argument('-v', '--verbose', help='Print debug log', action='store_true') 77 | parser.add_argument('-lf', '--logfile', help='Print log to pe-afl64.log rather than stream', action='store_true') 78 | parser.add_argument('pe_file', help='Target PE file for instrumentation') 79 | parser.add_argument('ida_dump', help='dump.txt from IDA (created by ida_dumper.py)') 80 | return parser.parse_args() 81 | 82 | 83 | def configure_logger(is_verbose: bool = False, to_file: bool = False): 84 | level = 'DEBUG' if is_verbose else 'INFO' 85 | logger_format = LOGGER_FORMAT_VERBOSE if is_verbose else LOGGER_FORMAT_NORMAL 86 | if to_file: 87 | logging.basicConfig(level=level, filename='pe-afl64.log', format=logger_format) 88 | else: 89 | logging.basicConfig(level=level, format=logger_format) 90 | 91 | 92 | def main(): 93 | args = parse_arguments() 94 | args.pe_afl = True 95 | configure_logger(args.verbose, args.logfile) 96 | if args.callback: 97 | raise ValueError("Callback instrumentation is not currently supported") 98 | # if args.callback and not instrument.is_driver(args.pe_file): 99 | # raise Exception("Callback support is on kernel drivers only") 100 | if args.thread_filter and not args.thread_entry: 101 | raise ValueError("Must provide thread entry address with the --thread-filter option") 102 | 103 | args.filter = False 104 | if is_driver(args.pe_file) and not args.thread_filter: 105 | args.filter = True 106 | 107 | if is_driver(args.pe_file) and not args.ntoskrnl and not args.nop: 108 | raise ValueError("When instrumenting a driver with PID-aware instrumentation, ntoskrnl path must be provided") 109 | 110 | if is_driver(args.pe_file): 111 | logging.info('Kernel-mode driver is being instrumented') 112 | else: 113 | logging.info('User-mode binary is being instrumented') 114 | logging.info('Single-thread instrument is on') 115 | 116 | pe, ida = instrument.initialize_instrumentation(args) 117 | if is_pe_32bit(pe): 118 | raise ValueError("32 bit will not work. Use the original pe-afl instead") 119 | 120 | main_sc, init_sc = init_shellcodes(args, args.ntoskrnl) 121 | args.snip_len = len(update_snippet_with_addr(0, main_sc)) 122 | # saves the indexes where the magics start 123 | args.sc_magic_offsets = [m.start() for m in re.finditer(pack_64bit(MAGIC), update_snippet_with_addr(0, main_sc))] 124 | if init_sc: 125 | args.init_sc_magic_offsets = [m.start() for m in re.finditer(pack_64bit(MAGIC), update_snippet_with_addr(0, init_sc))] 126 | 127 | # creating instrumentation shellcode with the updated address for each address 128 | shellcode_for_addr = {bb_addr: update_snippet_with_addr(bb_addr - pe.OPTIONAL_HEADER.ImageBase, main_sc) 129 | for bb_addr in ida['bb']} 130 | if args.thread_filter: 131 | shellcode_for_addr[args.thread_entry] = update_snippet_with_addr(args.thread_entry - pe.OPTIONAL_HEADER.ImageBase, init_sc) 132 | 133 | instrument.run(ida, args, shellcode_for_addr) 134 | 135 | 136 | if __name__ == '__main__': 137 | main() 138 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | capstone~=4.0.1 2 | keystone~=20.0.0 3 | keystone_engine==0.9.2 4 | lighthouse 5 | pwn==1.0 -------------------------------------------------------------------------------- /seh.py: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2022 Gal Kristal, Dina Teper 3 | # Copyright (C) 2022 SentinelOne, Inc. 4 | # 5 | # SPDX-License-Identifier: AGPL-3.0-or-later 6 | 7 | import struct 8 | 9 | g_c_handler = 0 10 | g_cpp_handler = 0 11 | g_gshandlercheck_seh_handler = 0 12 | 13 | UNW_FLAG_N_HANDLER = 0x0 14 | UNW_FLAG_E_HANDLER = 0x1 15 | UNW_FLAG_U_HANDLER = 0x2 16 | UNW_FLAG_CHAIN_INFO = 0x4 17 | 18 | 19 | class ExInfo: 20 | def __init__(self, begin_addr, end_addr, unwind_info, unwind_info_addr): 21 | self.begin_addr = begin_addr 22 | self.end_addr = end_addr 23 | self.unwind_info = unwind_info 24 | self.unwind_info_addr = unwind_info_addr 25 | 26 | def __str__(self): 27 | return str([hex(self.begin_addr), hex(self.end_addr), hex(self.unwind_info_addr), self.unwind_info]) 28 | 29 | def __repr__(self): 30 | return str([hex(self.begin_addr), hex(self.end_addr), hex(self.unwind_info_addr), self.unwind_info]) 31 | 32 | 33 | class ScopeEntry: 34 | def __init__(self, data): 35 | self.data = data 36 | self.begin, self.end, self.handler, self.target = struct.unpack('> 3 51 | self.size_of_prolog = size_of_prolog 52 | self.count_of_codes = count_of_codes 53 | self.frame_register = frame_reg_ofs & 0b1111 54 | self.frame_offset = (frame_reg_ofs & 0b11110000) >> 4 55 | self.exception_handler = None 56 | self.exception_handler_addr = None 57 | self.chained_exception = None 58 | self.chained_exception_addr = None 59 | self.scope_list = [] 60 | self.scope_tbl_start_rva = None 61 | self.count_of_scope_entries = None 62 | 63 | self._parse_data() 64 | 65 | def _parse_data(self): 66 | if self.flags & (UNW_FLAG_E_HANDLER | UNW_FLAG_U_HANDLER): 67 | self.exception_handler_addr = self.unwind_info_addr + 4 + 2 * ((self.count_of_codes + 1) & ~1) 68 | self.exception_handler, self.count_of_scope_entries = struct.unpack(' bool: 33 | for a, b in pairs: 34 | if a <= val < b: 35 | return True 36 | return False 37 | 38 | 39 | def get_closest_or_equal(seq: List, val) -> Optional[Any]: 40 | """ 41 | Returns the item closest to val (ret<=val) in an ordered list. 42 | :param seq: Ordered list 43 | :param val: Value to search 44 | :return: 45 | """ 46 | if val < seq[0]: 47 | return None 48 | return seq[bisect.bisect_right(seq, val) - 1] 49 | 50 | 51 | def remove_dos_stub(fname: str) -> str: 52 | """ 53 | Remove the DOS stub from the PE header to free some space for us in the headers. 54 | :param fname: PE name 55 | :return: New PE name 56 | """ 57 | pe = pefile.PE(fname) 58 | with open(fname, 'rb') as f: 59 | buf = f.read() 60 | replace = buf[pe.DOS_HEADER.e_lfanew:pe.OPTIONAL_HEADER.SizeOfHeaders] + b'\x00' * (pe.DOS_HEADER.e_lfanew - 0x40) 61 | buf = buf[:0x40 - 4] + struct.pack(' str: 71 | """ 72 | Remove the certificate from the PE as it's not needed any more. Creates a new PE file with the certificate removed. 73 | :param fname: PE name 74 | :return: New PE name 75 | """ 76 | pe = pefile.PE(fname) 77 | d = pe.get_directory_by_name('IMAGE_DIRECTORY_ENTRY_SECURITY') 78 | assert d.VirtualAddress + d.Size == pe.get_length(), 'some overlays behind certificate' 79 | dir_size = d.Size 80 | d.VirtualAddress = 0 81 | d.Size = 0 82 | name, extension = fname.rsplit('.', 1) 83 | name = name + '.no_certificate.' + extension 84 | pe.write(filename=name, cut=dir_size) 85 | pe.close() 86 | return name 87 | 88 | 89 | def fix_checksum(fname: str) -> None: 90 | """ 91 | recalculates and resets PE file's checksum 92 | :param fname: file name 93 | """ 94 | pe = pefile.PE(fname) 95 | pe.OPTIONAL_HEADER.CheckSum = pe.generate_checksum() 96 | pe.write(filename=fname) 97 | pe.close() 98 | 99 | 100 | class SortedSections: 101 | """ 102 | Sorted sections of a PE file. 103 | The class allows a quicker access to the sections by their file offset 104 | """ 105 | 106 | def __init__(self, pe: pefile.PE): 107 | self.sec_offsets = [] 108 | self.section_mapping: Dict[int, pefile.SectionStructure] = {} 109 | self.virtual_sec_offsets = [] 110 | self.virtual_section_mapping: Dict[int, pefile.SectionStructure] = {} 111 | self.max_virtual_offset = 0 112 | self.extend(pe.sections) 113 | 114 | def _update_virtual_offsets_list(self) -> None: 115 | """ 116 | When treating offsets to virtual addresses (found when Virtual Address is converted to offset using va2ofs) 117 | we need to look at the virtual size of each section. 118 | In this scenario, sections can overlap and sometimes be ignored completely. 119 | :return: None 120 | """ 121 | new_sec_virtual_offsets = [] 122 | new_virtual_section_mapping = {} 123 | max_offset = 0 124 | for sec_offset in self.sec_offsets: 125 | section = self.section_mapping[sec_offset] 126 | sec_end = sec_offset + section.Misc_VirtualSize 127 | if sec_offset >= max_offset: 128 | new_sec_virtual_offsets.append(sec_offset) 129 | new_virtual_section_mapping[sec_offset] = section 130 | max_offset = sec_end 131 | elif sec_end >= max_offset: 132 | new_sec_virtual_offsets.append(max_offset) 133 | new_virtual_section_mapping[max_offset] = section 134 | max_offset = sec_end 135 | self.virtual_sec_offsets = new_sec_virtual_offsets 136 | self.virtual_section_mapping = new_virtual_section_mapping 137 | self.max_virtual_offset = max_offset 138 | 139 | def extend(self, sections: Union[pefile.SectionStructure, List[pefile.SectionStructure]]) -> None: 140 | """ 141 | Extend the list of sections. 142 | :param sections: list of sections to add to the index 143 | :return: none 144 | """ 145 | if isinstance(sections, pefile.SectionStructure) and sections.get_PointerToRawData_adj() not in self.section_mapping: 146 | sec_start_offset = sections.get_PointerToRawData_adj() 147 | bisect.insort_right(self.sec_offsets, sec_start_offset) 148 | self.section_mapping[sec_start_offset] = sections 149 | elif isinstance(sections, list): 150 | for section in sections: 151 | if section not in self.sec_offsets: 152 | bisect.insort_right(self.sec_offsets, section.get_PointerToRawData_adj()) 153 | self.section_mapping[section.get_PointerToRawData_adj()] = section 154 | self._update_virtual_offsets_list() 155 | 156 | def remove(self, sections: Union[pefile.SectionStructure, List[pefile.SectionStructure]]) -> None: 157 | """ 158 | Remove sections from the list of sections. 159 | :param sections: sections to remove 160 | :return: none 161 | """ 162 | if isinstance(sections, 163 | pefile.SectionStructure) and sections.get_PointerToRawData_adj() in self.section_mapping: 164 | self.sec_offsets.remove(sections.get_PointerToRawData_adj()) 165 | self.section_mapping.pop(sections.get_PointerToRawData_adj()) 166 | elif isinstance(sections, list): 167 | for section in sections: 168 | if section in self.sec_offsets: 169 | self.sec_offsets.remove(section.get_PointerToRawData_adj()) 170 | self.section_mapping.pop(section.get_PointerToRawData_adj()) 171 | self._update_virtual_offsets_list() 172 | 173 | def get_sec_by_offset(self, offset: int, is_virtual_offset: bool = False) -> Optional[pefile.SectionStructure]: 174 | """ 175 | Get the section at the given offset. 176 | :param offset: offset to search for 177 | :param is_virtual_offset: is provided offset a virtual offset 178 | :return: section at the given offset if found, None otherwise 179 | """ 180 | # Boundaries check 181 | if offset < self.sec_offsets[0] or (is_virtual_offset and offset > self.max_virtual_offset): 182 | return None 183 | 184 | if is_virtual_offset: 185 | sec_offset = bisect.bisect_right(self.virtual_sec_offsets, offset) - 1 186 | return self.section_mapping.get(self.virtual_sec_offsets[sec_offset]) 187 | else: 188 | sec_offset = bisect.bisect_right(self.sec_offsets, offset) - 1 189 | return self.section_mapping.get(self.sec_offsets[sec_offset]) 190 | 191 | def reset_state(self): 192 | """ 193 | Reset the state of the index. 194 | :return: none 195 | """ 196 | self.sec_offsets = [] 197 | self.section_mapping = {} 198 | self.virtual_sec_offsets = [] 199 | self.virtual_section_mapping = {} 200 | -------------------------------------------------------------------------------- /winafl_example/afl-staticinstr.c: -------------------------------------------------------------------------------- 1 | /* 2 | WinAFL persistent loop implementation for statically instrumented target 3 | ----------------------------------------------------------------------- 4 | 5 | Written by Axel "0vercl0k" Souchet <0vercl0k@tuxfamily.org> 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at: 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | */ 13 | #include "afl-staticinstr.h" 14 | #include 15 | #include 16 | #include 17 | 18 | #ifdef __cplusplus 19 | extern "C" { 20 | #endif 21 | 22 | #define MAP_SIZE 65536 23 | #define STATIC_COV_SECTION_NAME ".cov" 24 | #define STATIC_COV_SECTION_NAME_LEN 4 25 | #define AFL_STATIC_CONFIG_ENV TEXT("AFL_STATIC_CONFIG") 26 | #define AFL_VARIABLE_BEHAVIOR_TRACES_BASE_DIR TEXT("C:\\test\\traces") 27 | #define AFL_VARIABLE_BEHAVIOR_ITERATIONS 20 28 | #define MAX_STRING_SIZE 64 29 | 30 | #pragma pack(push, 1) 31 | typedef struct { 32 | CHAR __afl_area[MAP_SIZE]; 33 | UINT_PTR __afl_prev_loc; 34 | UINT __tls_index; 35 | UINT __tls_slot_offset; 36 | PUCHAR __afl_area_ptr; 37 | } STATIC_COVERAGE_DATA, *PSTATIC_COVERAGE_DATA; 38 | #pragma pack(pop) 39 | 40 | // 41 | // The handle to the pipe used to talk with afl-fuzz.exe 42 | // 43 | 44 | HANDLE g_winafl_pipe = INVALID_HANDLE_VALUE; 45 | 46 | // 47 | // The no fuzzing mode is enabled when a binary is run without 48 | // passing the fuzzing configuration in the AFL_STATIC_CONFIG 49 | // environment variable (running a binary by itself, without 50 | // being run via afl-fuzz.exe will enable this mode for example). 51 | // Under this mode, the persistent loop exits after a single 52 | // iteration. 53 | // 54 | 55 | BOOL g_nofuzzing_mode = FALSE; 56 | 57 | // 58 | // The no instrumentation mode means the binary is running 59 | // without an AFL instrumented module in its address-space. 60 | // As a result, it means there is no coverage information 61 | // available (g_static_coverage_data is empty). This happens 62 | // when the persistent loop is run without instrumenting any 63 | // modules. 64 | // 65 | 66 | BOOL g_noinstrumentation = TRUE; 67 | 68 | // 69 | // The number of instrumented modules available in the 70 | // address space. 71 | // 72 | 73 | SIZE_T g_ninstrumented_modules = 0; 74 | 75 | // 76 | // The coverage data is a pointer to a structure that 77 | // can be found in an instrumented binary, in its '.syzyafl' 78 | // section. A pointer to the coverage map can be found in it, 79 | // but also what type of instrumentation it is using (single/multi thread). 80 | // Note that, it is NULL when g_noinstrumentation is TRUE. 81 | 82 | #define kMaximumInstrumentedModules 1 83 | STATIC_COVERAGE_DATA *g_static_coverage_data[kMaximumInstrumentedModules]; 84 | 85 | // 86 | // The current iterations track the number of iterations the persistent 87 | // loop has been through. 88 | // 89 | 90 | SIZE_T g_current_iterations = 0; 91 | 92 | // 93 | // The n iterations is the total number total iterations that 94 | // afl-fuzz.exe wants to be run every time the target process is 95 | // spawned. This is configured via the AFL_STATIC_CONFIG environment 96 | // variable. 97 | // 98 | 99 | SIZE_T g_niterations = 0; 100 | 101 | // 102 | // Some synchronization primitives. 103 | // 104 | 105 | CRITICAL_SECTION g_crit_section; 106 | INIT_ONCE g_init_once = INIT_ONCE_STATIC_INIT, g_init_once_bareminimum = INIT_ONCE_STATIC_INIT; 107 | 108 | LONG CALLBACK __afl_VectoredHandler(PEXCEPTION_POINTERS ExceptionInfo) 109 | 110 | /*++ 111 | 112 | Routine Description: 113 | 114 | Catch exceptions and let afl-fuzz.exe know when an interesting 115 | one happened. 116 | 117 | Arguments: 118 | 119 | ExceptionInfo - A structure with information about the exception 120 | that triggered the invocation of the vectored exception handler. 121 | 122 | Return Value: 123 | 124 | EXCEPTION_CONTINUE_SEARCH if exception not handled. 125 | 126 | --*/ 127 | 128 | 129 | { 130 | DWORD Dummy; 131 | EnterCriticalSection(&g_crit_section); 132 | 133 | if( 134 | ExceptionInfo->ExceptionRecord->ExceptionCode == DBG_PRINTEXCEPTION_C 135 | #ifdef DBG_PRINTEXCEPTION_WIDE_C 136 | 137 | // 138 | // This define has been introduced in the Windows 10 SDK and doesn't 139 | // exist in older SDKs. 140 | // 141 | 142 | || ExceptionInfo->ExceptionRecord->ExceptionCode == DBG_PRINTEXCEPTION_WIDE_C 143 | #endif 144 | ) { 145 | _tprintf(TEXT("[*] Received an OutputDebugString exception.\n")); 146 | } 147 | else if(ExceptionInfo->ExceptionRecord->ExceptionCode == 0xE06D7363) { 148 | 149 | // 150 | // https://support.microsoft.com/fr-fr/help/185294/prb-exception-code-0xe06d7363-when-calling-win32-seh-apis 151 | // https://blogs.msdn.microsoft.com/oldnewthing/20100730-00/?p=13273 152 | // 153 | 154 | _tprintf(TEXT("[*] Received an MSVC C++ exception.\n")); 155 | } 156 | else { 157 | _tprintf(TEXT("[*] The program just crashed.\n")); 158 | if(g_nofuzzing_mode == FALSE) { 159 | WriteFile(g_winafl_pipe, "C", 1, &Dummy, NULL); 160 | TerminateProcess(GetCurrentProcess(), 0); 161 | } 162 | } 163 | 164 | LeaveCriticalSection(&g_crit_section); 165 | _tprintf(TEXT("[+] Passing it to the program (might trigger a JIT debugger if it can't handle it).\n")); 166 | return EXCEPTION_CONTINUE_SEARCH; 167 | } 168 | 169 | VOID __afl_display_banner() 170 | 171 | /*++ 172 | 173 | Routine Description: 174 | 175 | Displays the AFL persistent loop banner. 176 | 177 | Arguments: 178 | 179 | None. 180 | 181 | Return Value: 182 | 183 | None. 184 | 185 | --*/ 186 | 187 | { 188 | _tprintf(TEXT("Persistent loop implementation by <0vercl0k@tuxfamily.org>\n")); 189 | _tprintf(TEXT("Based on WinAFL by \n")); 190 | } 191 | 192 | BOOL CALLBACK __afl_set_it_up( 193 | PINIT_ONCE InitOnce, PVOID Parameter, PVOID *Context 194 | ) 195 | 196 | /*++ 197 | 198 | Routine Description: 199 | 200 | Sets up the environment: creates the pipe to talk with afl-fuzz.exe, 201 | maps the coverage byte-map that afl-fuzz.exe will map in and fix-up 202 | the instrumented module so that its coverage byte-map pointer points 203 | inside the shared memory section. 204 | 205 | Arguments: 206 | 207 | InitOnce - Unused. 208 | 209 | Parameter - Unused. 210 | 211 | Context - Unused. 212 | 213 | Return Value: 214 | 215 | TRUE on success, FALSE otherwise. 216 | 217 | --*/ 218 | 219 | { 220 | BOOL Status = TRUE; 221 | HANDLE MappedFile = NULL; 222 | PVOID AreaPtr = NULL; 223 | DWORD SizeNeeded; 224 | HMODULE Modules[128]; 225 | SIZE_T i = 0; 226 | TCHAR PipeName[MAX_STRING_SIZE], ShmName[MAX_STRING_SIZE], 227 | FuzzerId[MAX_STRING_SIZE], StaticConfig[MAX_STRING_SIZE], 228 | InstrumentedModuleName[MAX_STRING_SIZE]; 229 | 230 | UNREFERENCED_PARAMETER(InitOnce); 231 | UNREFERENCED_PARAMETER(Parameter); 232 | UNREFERENCED_PARAMETER(Context); 233 | 234 | EnterCriticalSection(&g_crit_section); 235 | 236 | // 237 | // Let's first figure out if we are running with any instrumented module, 238 | // in the address space. 239 | // If not, we turn on the no instrumentation switch. 240 | // 241 | 242 | Status = EnumProcessModulesEx(GetCurrentProcess(), Modules, sizeof(Modules), &SizeNeeded, LIST_MODULES_ALL); 243 | 244 | if(Status == FALSE) { 245 | _tprintf(TEXT("[-] EnumProcessModulesEx failed - too many modules loaded?.\n")); 246 | TerminateProcess(GetCurrentProcess(), 0); 247 | } 248 | 249 | for(i = 0; i < SizeNeeded / sizeof(Modules[0]); ++i) { 250 | PVOID Base = (PVOID)Modules[i]; 251 | PIMAGE_NT_HEADERS NtHeaders = (PIMAGE_NT_HEADERS)((PUCHAR)Base + ((PIMAGE_DOS_HEADER)Base)->e_lfanew); 252 | PIMAGE_SECTION_HEADER Sections = (PIMAGE_SECTION_HEADER)(NtHeaders + 1); 253 | USHORT j = 0; 254 | 255 | for(j = 0; j < NtHeaders->FileHeader.NumberOfSections; ++j) { 256 | if(memcmp(Sections[j].Name, STATIC_COV_SECTION_NAME, STATIC_COV_SECTION_NAME_LEN) != 0) { 257 | continue; 258 | } 259 | 260 | // 261 | // Make sure we haven't exhausted the number of slots for the static coverage 262 | // information. 263 | // 264 | 265 | if(g_ninstrumented_modules == ARRAYSIZE(g_static_coverage_data)) { 266 | _tprintf( 267 | TEXT("[!] You have exhausted the number of instrumented modules (%d).\n"), 268 | g_ninstrumented_modules 269 | ); 270 | break; 271 | } 272 | 273 | GetModuleBaseName(GetCurrentProcess(), Modules[i], InstrumentedModuleName, MAX_STRING_SIZE); 274 | g_static_coverage_data[g_ninstrumented_modules] = (STATIC_COVERAGE_DATA*)( 275 | (UINT_PTR)(Sections[j].VirtualAddress) + (UINT_PTR)Base 276 | ); 277 | memset(g_static_coverage_data[g_ninstrumented_modules]->__afl_area, 0, MAP_SIZE + 8); 278 | 279 | _tprintf( 280 | TEXT("[+] Found a statically instrumented module: %s (%s thread mode).\n"), 281 | InstrumentedModuleName, 282 | (g_static_coverage_data[g_ninstrumented_modules]->__tls_slot_offset == 0) ? 283 | TEXT("single") : TEXT("multi") 284 | ); 285 | g_ninstrumented_modules++; 286 | g_noinstrumentation = FALSE; 287 | break; 288 | } 289 | } 290 | 291 | if(g_noinstrumentation == TRUE) { 292 | _tprintf(TEXT("[-] No instrumented module found.\n")); 293 | Status = FALSE; 294 | } 295 | 296 | // 297 | // Let's figure out, if afl-fuzz.exe spawned us or not? 298 | // If not, we can switch on the no fuzzing mode and exit. 299 | // 300 | 301 | if(GetEnvironmentVariable(AFL_STATIC_CONFIG_ENV, StaticConfig, MAX_STRING_SIZE) == 0) { 302 | _tprintf(TEXT("[-] Not running under afl-fuzz.exe.\n")); 303 | g_nofuzzing_mode = TRUE; 304 | Status = FALSE; 305 | goto clean; 306 | } 307 | 308 | // 309 | // We are running under afl-fuzz.exe; let's open the pipe used for 310 | // communication, create a named shared memory section to store the coverage 311 | // data and fix-up the instrumented module so that its instrumentation writes 312 | // in the shared memory section's content. 313 | // 314 | 315 | memset(PipeName, 0, MAX_STRING_SIZE * sizeof(PipeName[0])); 316 | memset(ShmName, 0, MAX_STRING_SIZE * sizeof(ShmName[0])); 317 | 318 | _tprintf(TEXT("[*] Setting up the environment (%s)..\n"), StaticConfig); 319 | if(_stscanf_s(StaticConfig, TEXT("%[a-zA-Z0-9]:%u"), FuzzerId, _countof(FuzzerId), &g_niterations) != 2) { 320 | _tprintf( 321 | TEXT("[-] The ") AFL_STATIC_CONFIG_ENV TEXT(" environment variable isn't properly formated.\n") 322 | ); 323 | Status = FALSE; 324 | goto clean; 325 | } 326 | 327 | _stprintf_s(PipeName, _countof(PipeName), TEXT("\\\\.\\pipe\\afl_pipe_%s"), FuzzerId); 328 | _stprintf_s(ShmName, _countof(ShmName), TEXT("afl_shm_%s"), FuzzerId); 329 | 330 | // 331 | // Connect to the named pipe. 332 | // 333 | 334 | g_winafl_pipe = CreateFile( 335 | PipeName, // pipe name 336 | GENERIC_READ | GENERIC_WRITE, // read and write access 337 | 0, // no sharing 338 | NULL, // default security attributes 339 | OPEN_EXISTING, // opens existing pipe 340 | 0, // default attributes 341 | NULL // no template file 342 | ); 343 | 344 | if(g_winafl_pipe == INVALID_HANDLE_VALUE) { 345 | _tprintf(TEXT("[-] Opening the named pipe failed.\n")); 346 | Status = FALSE; 347 | goto clean; 348 | } 349 | 350 | // 351 | // Get the named shared memory section mapped. 352 | // 353 | 354 | MappedFile = OpenFileMapping( 355 | FILE_MAP_ALL_ACCESS, 356 | FALSE, 357 | ShmName 358 | ); 359 | 360 | if(MappedFile == NULL) { 361 | _tprintf(TEXT("[-] Opening the file mapping failed.\n")); 362 | Status = FALSE; 363 | goto clean; 364 | } 365 | 366 | AreaPtr = MapViewOfFile( 367 | MappedFile, 368 | FILE_MAP_ALL_ACCESS, 369 | 0, 370 | 0, 371 | MAP_SIZE 372 | ); 373 | 374 | if(AreaPtr == NULL) { 375 | _tprintf(TEXT("[-] Mapping a view of the shared memory section failed.\n")); 376 | Status = FALSE; 377 | goto clean; 378 | } 379 | 380 | // 381 | // Fix up the instrumented modules so that the pointer storing the base 382 | // of the coverage map points to the shared memory section we just mapped in. 383 | // The instrumented code will now write the coverage information directly 384 | // in the shared section. 385 | // 386 | 387 | for(i = 0; i < g_ninstrumented_modules; ++i) { 388 | g_static_coverage_data[i]->__afl_area_ptr = (PUCHAR)AreaPtr; 389 | } 390 | 391 | _tprintf(TEXT("[+] Fixed-up the %d instrumented modules.\n"), g_ninstrumented_modules); 392 | 393 | clean: 394 | 395 | if(g_nofuzzing_mode == FALSE && g_noinstrumentation == TRUE) { 396 | 397 | // 398 | // It means there is no instrumented module in the address space, 399 | // and we are being run through AFL..weird. Display a pop-up! 400 | // 401 | 402 | _tprintf(TEXT("[-] You are running without instrumentation under afl-fuzz.exe.\n")); 403 | 404 | MessageBox( 405 | NULL, 406 | TEXT("You are running without instrumentation under afl-fuzz.exe."), 407 | NULL, 408 | MB_OK | MB_ICONERROR 409 | ); 410 | } 411 | 412 | if(MappedFile != NULL) { 413 | CloseHandle(MappedFile); 414 | } 415 | 416 | LeaveCriticalSection(&g_crit_section); 417 | return Status; 418 | } 419 | 420 | BOOL CALLBACK __afl_set_up_bareminimum( 421 | PINIT_ONCE InitOnce, PVOID Parameter, PVOID *Context 422 | ) 423 | 424 | /*++ 425 | 426 | Routine Description: 427 | 428 | Installs the vectored exception handler to ease reproducability. 429 | The VEH gets installed even if running without afl-fuzz.exe, or 430 | if running a non-instrumented module. This is particularly useful 431 | for debugging issues found by afl-fuzz.exe on a vanilla target (in 432 | the case the debugging symbols are a bit funky on an instrumented 433 | binary for example). Also initialize the critical section. 434 | 435 | Arguments: 436 | 437 | InitOnce - Unused. 438 | 439 | Parameter - Unused. 440 | 441 | Context - Unused. 442 | 443 | Return Value: 444 | 445 | TRUE. 446 | 447 | --*/ 448 | 449 | { 450 | UNREFERENCED_PARAMETER(InitOnce); 451 | UNREFERENCED_PARAMETER(Parameter); 452 | UNREFERENCED_PARAMETER(Context); 453 | 454 | InitializeCriticalSection(&g_crit_section); 455 | 456 | // 457 | // Set up the exception handler. 458 | // 459 | 460 | AddVectoredExceptionHandler(0, __afl_VectoredHandler); 461 | 462 | // 463 | // Display the banner to know the persistent loop is here. 464 | // 465 | 466 | __afl_display_banner(); 467 | return TRUE; 468 | } 469 | 470 | BOOL __afl_persistent_loop() 471 | 472 | /*++ 473 | 474 | Routine Description: 475 | 476 | Persistent loop implementation. 477 | 478 | Arguments: 479 | 480 | None. 481 | 482 | Return Value: 483 | 484 | TRUE until the iteration count gets hit, and then FALSE. 485 | 486 | --*/ 487 | 488 | { 489 | BOOL Status; 490 | CHAR Command = 0; 491 | DWORD Dummy; 492 | SIZE_T i = 0; 493 | 494 | if(g_nofuzzing_mode == TRUE) { 495 | 496 | // 497 | // Force exit at the first iteration when afl-fuzz isn't detected 498 | // to fake "normal" execution of instrumented binary. 499 | // 500 | 501 | Status = FALSE; 502 | goto clean; 503 | } 504 | 505 | Status = InitOnceExecuteOnce( 506 | &g_init_once_bareminimum, 507 | __afl_set_up_bareminimum, 508 | NULL, 509 | NULL 510 | ); 511 | 512 | Status = InitOnceExecuteOnce( 513 | &g_init_once, 514 | __afl_set_it_up, 515 | NULL, 516 | NULL 517 | ); 518 | 519 | if(Status == FALSE) { 520 | _tprintf(TEXT("[+] Enabling the no fuzzing mode.\n")); 521 | g_nofuzzing_mode = TRUE; 522 | Status = TRUE; 523 | goto clean; 524 | } 525 | 526 | // 527 | // If this not the first time, it means we have to signal afl-fuzz that 528 | // the previous test-case ended. 529 | // 530 | 531 | if(g_current_iterations > 0) { 532 | WriteFile(g_winafl_pipe, "K", 1, &Dummy, NULL); 533 | } 534 | 535 | if(g_current_iterations == g_niterations) { 536 | 537 | // 538 | // It is time to stop the machine! 539 | // 540 | 541 | CloseHandle(g_winafl_pipe); 542 | g_winafl_pipe = INVALID_HANDLE_VALUE; 543 | 544 | UnmapViewOfFile(g_static_coverage_data[0]->__afl_area_ptr); 545 | 546 | // 547 | // Redirect the coverage map back into the instrumented binary's 548 | // .syzyafl section so that the program doesn't crash while exiting. 549 | // 550 | 551 | for(i = 0; i < g_ninstrumented_modules; ++i) { 552 | g_static_coverage_data[i]->__afl_area_ptr = (PUCHAR)g_static_coverage_data[i]->__afl_area; 553 | } 554 | 555 | Status = FALSE; 556 | goto clean; 557 | } 558 | 559 | // 560 | // Tell afl-fuzz that we are ready for the next iteration. 561 | // 562 | if (g_current_iterations == 0) { 563 | g_current_iterations+=1; 564 | return TRUE; 565 | } 566 | memcpy(g_static_coverage_data[0]->__afl_area_ptr, g_static_coverage_data[0]->__afl_area, MAP_SIZE); 567 | WriteFile(g_winafl_pipe, "P", 1, &Dummy, NULL); 568 | 569 | // 570 | // Wait until we have the go from afl-fuzz to go ahead (below call is blocking). 571 | // 572 | 573 | ReadFile(g_winafl_pipe, &Command, 1, &Dummy, NULL); 574 | if(Command != 'F') { 575 | if(Command == 'Q') { 576 | _tprintf(TEXT("[+] Received the quit signal, exiting.\n")); 577 | } else { 578 | _tprintf(TEXT("[-] Received an unknown command from afl-fuzz, exiting (%.2x).\n"), Command); 579 | } 580 | 581 | TerminateProcess(GetCurrentProcess(), 0); 582 | } 583 | 584 | clean: 585 | 586 | g_current_iterations++; 587 | 588 | #ifdef AFL_STATIC_VARIABLE_BEHAVIOR_DEBUG 589 | 590 | { 591 | // 592 | // To ease debugging of variable behavior, we fake a configuration 593 | // where we run 10 iterations through the persistent loop, and 594 | // we save to disk the coverage map at every iterations. 595 | // You can then diff them and understand what parts of the map 596 | // get changed, and you can then set hardware write access breakpoints 597 | // to see what is the code writing in the coverage map. 598 | // 599 | 600 | FILE *CoverageFile = NULL; 601 | TCHAR CoverageFilename[MAX_STRING_SIZE]; 602 | 603 | if(g_current_iterations == 1) { 604 | 605 | // 606 | // Check various things on the first iteration. This is the 607 | // only time this block will get executed. 608 | // 609 | 610 | // 611 | // We cannot run in this mode if no instrumentation has been found, as 612 | // there won't be any coverage map. 613 | // 614 | 615 | if(g_noinstrumentation == TRUE) { 616 | _tprintf(TEXT( 617 | "[-] Cannot run the variable behavior debugging mode without an instrumented module.\n" 618 | )); 619 | Status = FALSE; 620 | goto end; 621 | } 622 | 623 | // 624 | // We cannot run in this mode if we are fuzzing right now, display a message 625 | // box as - most likely - afl-fuzz.exe is sink-holing stdout messages. 626 | // 627 | 628 | if(g_nofuzzing_mode == FALSE) { 629 | MessageBox( 630 | NULL, 631 | TEXT("You are running the target under afl-fuzz.exe with the variable behavior debugging mode."), 632 | NULL, 633 | MB_OK | MB_ICONERROR 634 | ); 635 | Status = FALSE; 636 | goto end; 637 | } 638 | 639 | // 640 | // Let the user knows that the variable behavior debugging mode is enabled, 641 | // and configure the number of iterations. 642 | // 643 | 644 | _tprintf(TEXT("[+] Enabled the variable behavior debugging mode.\n")); 645 | g_niterations = AFL_VARIABLE_BEHAVIOR_ITERATIONS; 646 | 647 | // 648 | // Fix-up all the coverage maps to point into the first one's. 649 | // 650 | 651 | for(i = 1; i < g_ninstrumented_modules; ++i) { 652 | g_static_coverage_data[i]->__afl_area_ptr = (PUCHAR)g_static_coverage_data[0]->__afl_area; 653 | } 654 | 655 | if(IsDebuggerPresent()) { 656 | 657 | // 658 | // If we are under a debugger, let's give to the user the 659 | // coverage map base address as it is useful to break on write 660 | // access to certain bytes in the map to investigate variable 661 | // behaviors test-cases. 662 | // 663 | 664 | _tprintf( 665 | TEXT("[+] Coverage map base: %p.\n"), 666 | g_static_coverage_data[0]->__afl_area_ptr 667 | ); 668 | 669 | // 670 | // Also breaking so that the user can set its breakpoints before 671 | // we start running the persistent loop. 672 | // 673 | 674 | __debugbreak(); 675 | } 676 | } 677 | 678 | // 679 | // Force the persistent loop to run again if we need it to. 680 | // 681 | 682 | if(g_current_iterations == g_niterations) { 683 | 684 | // 685 | // We are done! 686 | // 687 | 688 | Status = FALSE; 689 | } else { 690 | 691 | // 692 | // Force re-entering the persistent loop again. 693 | // 694 | 695 | g_nofuzzing_mode = FALSE; 696 | } 697 | 698 | if(g_current_iterations > 1 && !IsDebuggerPresent()) { 699 | 700 | // 701 | // Write the coverage map to disk if this is not the first 702 | // iteration as the instrumented code didn't get a chance 703 | // to be executed yet. We also don't overwrite the traces if a 704 | // debugger is attached, as it most likely means that an 705 | // investigation is on-going. 706 | // 707 | 708 | _stprintf_s( 709 | CoverageFilename, _countof(CoverageFilename), 710 | AFL_VARIABLE_BEHAVIOR_TRACES_BASE_DIR L"\\%u.bin", g_current_iterations - 1 711 | ); 712 | 713 | if(_tfopen_s(&CoverageFile, CoverageFilename, TEXT("wb"))) { 714 | _tprintf(TEXT("[-] Cannot open %s.\n"), CoverageFilename); 715 | } else { 716 | fwrite(g_static_coverage_data[0]->__afl_area_ptr, MAP_SIZE, 1, CoverageFile); 717 | fclose(CoverageFile); 718 | } 719 | } 720 | } 721 | 722 | end: 723 | 724 | #endif 725 | 726 | if(g_noinstrumentation == FALSE) { 727 | 728 | // 729 | // Reset the global state only if we have found an instrumented 730 | // module earlier - otherwise the g_static_coverage_data array 731 | // is empty. 732 | // 733 | 734 | for(i = 0; i < g_ninstrumented_modules; ++i) { 735 | PUINT PerThreadPrevLoc; 736 | STATIC_COVERAGE_DATA *CurrentCoverageData = g_static_coverage_data[i]; 737 | if(CurrentCoverageData->__tls_slot_offset != 0) { 738 | 739 | // 740 | // TLS version if we are fuzzing a multithread instrumented binary. 741 | // 742 | 743 | PUINT Base = (PUINT)(__readgsqword(0x2C) + (4 * CurrentCoverageData->__tls_index)); 744 | PerThreadPrevLoc = (PUINT)(*Base + CurrentCoverageData->__tls_slot_offset); 745 | } else { 746 | PerThreadPrevLoc = (PUINT)(&CurrentCoverageData->__afl_prev_loc); 747 | } 748 | 749 | *PerThreadPrevLoc = 0; 750 | } 751 | 752 | memset(g_static_coverage_data[0]->__afl_area, 0, MAP_SIZE + 8); 753 | 754 | } 755 | 756 | #ifdef AFL_STATIC_VARIABLE_BEHAVIOR_DEBUG 757 | 758 | // 759 | // Make sure to reinitialize the counter to 0 in order 760 | // to not exhaust all the slots. 761 | // 762 | 763 | g_ninstrumented_modules = 0; 764 | 765 | #endif 766 | memset(g_static_coverage_data[0]->__afl_area, 0, MAP_SIZE + 8); 767 | return Status; 768 | } 769 | 770 | #ifdef __cplusplus 771 | } 772 | #endif 773 | -------------------------------------------------------------------------------- /winafl_example/afl-staticinstr.h: -------------------------------------------------------------------------------- 1 | /* 2 | WinAFL persistent loop implementation for statically instrumented target 3 | ----------------------------------------------------------------------- 4 | 5 | Written by Axel "0vercl0k" Souchet <0vercl0k@tuxfamily.org> 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at: 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | This header is the glue you need to make afl-fuzz and your statically 14 | instrumented target play nice together. 15 | 16 | The entry-point __afl_persistent_loop is meant to be called at the start of the harness, 17 | in a loop like below. The function will set up everything needed to communicate 18 | and synchronize with afl-fuzz - if it is present (named pipe, shm, etc). 19 | 20 | while(__afl_persistent_loop()) { 21 | // init state 22 | // exercise target 23 | // clear state 24 | } 25 | 26 | If afl-fuzz isn't detected, then the function will simply return TRUE the first 27 | time so that the body gets executed once. 28 | */ 29 | #pragma once 30 | #include 31 | #include 32 | #include 33 | 34 | #if defined(_M_X64) || defined(__amd64__) 35 | //#error Static instrumentation is only available for 32 bit binaries 36 | #endif 37 | 38 | // 39 | // Enable the variable behavior debugging mode. 40 | // 41 | 42 | // #define AFL_STATIC_VARIABLE_BEHAVIOR_DEBUG 43 | 44 | #ifdef __cplusplus 45 | extern "C" { 46 | #endif 47 | 48 | BOOL __afl_persistent_loop(); 49 | 50 | #ifdef __cplusplus 51 | } 52 | #endif 53 | -------------------------------------------------------------------------------- /winafl_example/example.c: -------------------------------------------------------------------------------- 1 | #include "afl-staticinstr.h" 2 | #include 3 | 4 | typedef BOOL(*_fuzzMe)(char* input); 5 | 6 | 7 | #define SBI 8 | 9 | int main(int argc, char * argv[]) 10 | { 11 | if (argc < 2) { 12 | printf("Usage: harness.exe \n"); 13 | return -1; 14 | } 15 | 16 | HANDLE target = LoadLibrary(L"target.instrumented.dll"); 17 | 18 | if (!target) { 19 | printf("[-] Failed opening target dll: %d\n", GetLastError()); 20 | MessageBoxA(NULL, "Failed opening target dll", NULL, MB_OK | MB_ICONERROR); 21 | return 0; 22 | } 23 | printf("[+] Opened target dll\n"); 24 | _fuzzMe fuzzMe = (_fuzzMe)GetProcAddress(target, "fuzzMe"); 25 | if (!fuzzMe) { 26 | printf("[-] Failed getting target func: %d\n", GetLastError()); 27 | MessageBoxA(NULL, "Failed getting target func", NULL, MB_OK | MB_ICONERROR); 28 | return 0; 29 | } 30 | 31 | PVOID Base = (PVOID)target; 32 | PIMAGE_NT_HEADERS NtHeaders = (PIMAGE_NT_HEADERS)((PUCHAR)Base + ((PIMAGE_DOS_HEADER)Base)->e_lfanew); 33 | PIMAGE_SECTION_HEADER Sections = (PIMAGE_SECTION_HEADER)(NtHeaders + 1); 34 | USHORT j = 0; 35 | UINT_PTR base = 0; 36 | 37 | for (j = 0; j < NtHeaders->FileHeader.NumberOfSections; ++j) { 38 | if (memcmp(Sections[j].Name, ".cov", 4) != 0) { 39 | continue; 40 | } 41 | base = (UINT_PTR)(Sections[j].VirtualAddress) + (UINT_PTR)Base; 42 | memset(base, 0, 0x10000 + 8); 43 | } 44 | 45 | HANDLE hMapFile = OpenFileMappingA(FILE_MAP_ALL_ACCESS, FALSE, argv[1]); 46 | if (!hMapFile) { 47 | printf("[-] Failed opening the file mapping: %d\n", GetLastError()); 48 | //MessageBoxA(NULL, "Failed opening the file mapping", NULL, MB_OK | MB_ICONERROR); 49 | return 0; 50 | } 51 | LPVOID buf = MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, 1 << 16); 52 | if (!buf) { 53 | printf("[-] Failed MapViewOfFile: %d\n", GetLastError()); 54 | //MessageBoxA(NULL, "Failed MapViewOfFile", NULL, MB_OK | MB_ICONERROR); 55 | return 0; 56 | } 57 | 58 | int len = 0; 59 | char volatile fuzzbuf[4096] = { 0 }; 60 | 61 | while (__afl_persistent_loop()) { 62 | fuzzMe((UINT_PTR)buf + 4); 63 | } 64 | 65 | printf("End\n"); 66 | return 0; 67 | } 68 | --------------------------------------------------------------------------------