Last active
December 8, 2020 04:57
-
-
Save pavel-a/090cbe4b2aea9a054c55974b7c7be634 to your computer and use it in GitHub Desktop.
Writable code section fixer
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#pragma once | |
#define _WIN32_WINNT NTDDI_WINXP | |
#include <SDKDDKVer.h> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//////////////////////////////////////////////////////////////////////////////// | |
// WC-fixer | |
// | |
// Fixes writable code section attributes in PE files | |
// | |
//////////////////////////////////////////////////////////////////////////////// | |
#include "targetver.h" | |
#include <stdio.h> | |
#include <tchar.h> | |
#include <Windows.h> | |
#include <imagehlp.h> | |
#pragma comment(lib, "imagehlp") | |
#define dprint(fmt, ...) printf(fmt, __VA_ARGS__) | |
#ifndef _A_NOISE_DBG | |
#define _A_NOISE_DBG 1 | |
#endif | |
#if _A_NOISE_DBG | |
#define d2print(fmt, ...) dprint(fmt, __VA_ARGS__) | |
#define d2tprint(tfmt, ...) dtprint(tfmt, __VA_ARGS__) | |
#else | |
#define d2print(fmt, ...) __noop(fmt, __VA_ARGS__) | |
#define d2tprint(tfmt, ...) __noop(tfmt, __VA_ARGS__) | |
#endif //_A_NOISE_DBG | |
#if ( _A_NOISE_DBG > 1 ) | |
#define d3print d2print | |
#define d3tprint d2tprint | |
#else | |
#define d3print(fmt, ...) __noop(fmt, __VA_ARGS__) | |
#define d3tprint(tfmt, ...) __noop(tfmt, __VA_ARGS__) | |
#endif //_A_NOISE_DBG | |
#if UNICODE | |
// only non-unicode form of MapAndLoad exists in imagehlp. Grr. | |
BOOL MapAndLoadW(__in PCWSTR ImageName, | |
__in_opt PCWSTR DllPath, | |
__out PLOADED_IMAGE LoadedImage, | |
__in BOOL DotDll, | |
__in BOOL ReadOnly | |
); | |
#define MapAndLoadT MapAndLoadW | |
#else | |
#define MapAndLoadT MapAndLoad | |
#endif | |
PIMAGE_DATA_DIRECTORY getImageDataDirectory(PLOADED_IMAGE pim) | |
{ | |
PIMAGE_NT_HEADERS pnth = pim->FileHeader; | |
PIMAGE_FILE_HEADER pfh = &(pnth->FileHeader); | |
WORD mtype = pfh->Machine; | |
PIMAGE_DATA_DIRECTORY pd = NULL; | |
switch (mtype) { | |
case IMAGE_FILE_MACHINE_I386: { | |
PIMAGE_OPTIONAL_HEADER32 pih = | |
&(((PIMAGE_NT_HEADERS32)pnth)->OptionalHeader); | |
pd = pih->DataDirectory; | |
break; | |
} | |
case IMAGE_FILE_MACHINE_AMD64: { | |
// EXPERIMENTAL for x64 image mapped on x86 host: | |
PIMAGE_OPTIONAL_HEADER64 pih = | |
&(((PIMAGE_NT_HEADERS64)pnth)->OptionalHeader); | |
pd = pih->DataDirectory; | |
break; | |
} | |
case IMAGE_FILE_MACHINE_ARM: | |
default: | |
dprint("Unsupported arch: %#x\n", mtype); return false; | |
} | |
return pd; | |
} | |
bool clearWritableCode(PLOADED_IMAGE pim) | |
{ | |
PIMAGE_DATA_DIRECTORY pd = getImageDataDirectory(pim); | |
if (!pd) return false; | |
for (ULONG i = 0; i < pim->NumberOfSections; i++) { | |
PIMAGE_SECTION_HEADER sh = &pim->Sections[i]; | |
if (sh->Characteristics & IMAGE_SCN_CNT_CODE) { | |
if (sh->Characteristics & IMAGE_SCN_MEM_WRITE) { | |
printf("Writable code section found: [%.8s]\n", sh->Name); | |
sh->Characteristics &= ~IMAGE_SCN_MEM_WRITE; | |
} | |
} | |
} | |
return true; | |
} | |
bool removePdbPath(PLOADED_IMAGE pim) | |
{ | |
bool ret = false; | |
do { | |
PIMAGE_DATA_DIRECTORY pd = getImageDataDirectory(pim); | |
ULONG32 adebug = pd[IMAGE_DIRECTORY_ENTRY_DEBUG].VirtualAddress; | |
if (!pd) break; | |
PIMAGE_DEBUG_DIRECTORY pdebug = NULL; | |
for (ULONG i = 0; i < pim->NumberOfSections; i++) { | |
PIMAGE_SECTION_HEADER sh = &pim->Sections[i]; | |
if ((sh->VirtualAddress <= adebug) && | |
(sh->SizeOfRawData + sh->VirtualAddress > adebug)) { | |
adebug -= sh->VirtualAddress; | |
adebug += sh->PointerToRawData; | |
pdebug = (PIMAGE_DEBUG_DIRECTORY)(adebug + (PUCHAR)(pim->MappedAddress)); | |
break; | |
} | |
} | |
if (!pdebug) { | |
d2print("virt address of debug section not found\n"); | |
break; | |
} | |
if (pdebug->Type != IMAGE_DEBUG_TYPE_CODEVIEW /*2*/) | |
break; | |
char *pdd = pdebug->PointerToRawData + (char*)(pim->MappedAddress); | |
if (*((PULONG32)pdd) != 'SDSR') | |
break; | |
unsigned dsize = pdebug->SizeOfData; | |
if (dsize <= 0x18) //? | |
break; | |
pdd += 0x18; //skip header to the pdb name string | |
dsize -= 0x18; | |
unsigned dsize2 = (unsigned)strnlen(pdd, dsize); | |
if (dsize2 == 0 || dsize2 >= dsize) | |
break; | |
d2print("PDB path in image:[%hs]\n", pdd); | |
char * pdbname = strrchr(pdd, '\\'); | |
if (!pdbname || (pdbname - pdd) <= 4 || strchr(pdd, '\\') == pdbname) { | |
d2print("PDB path too short, not changing\n"); | |
ret = true; | |
break; | |
} | |
memcpy(pdd, "\\x\\", 3); | |
strcpy_s(pdd + 3, dsize2 - 3, pdbname); | |
char *t = pdd + strlen(pdd); | |
SecureZeroMemory(t, dsize2 - (t - pdd)); | |
ret = true; | |
} while (0); | |
return ret; | |
} | |
bool processFile(LPCTSTR fname, bool fRemovePdbPath) | |
{ | |
BOOL r; | |
LOADED_IMAGE im; | |
r = ::MapAndLoadT( | |
fname, | |
_T("\\no-implicit-paths"), | |
&im, | |
FALSE, // .exe by default | |
FALSE // readonly | |
); | |
if (!r) { | |
dprint("err open file for rechecksum %d\n", GetLastError()); | |
return FALSE; | |
} | |
if (!(im.Characteristics & IMAGE_FILE_EXECUTABLE_IMAGE)) { | |
dprint("error: the file not marked as executable (build errors?)\n"); | |
::UnMapAndLoad(&im); | |
return FALSE; | |
} | |
// begin image patches | |
if ( !clearWritableCode(&im)) { | |
dprint("error while editing image\n"); | |
::UnMapAndLoad(&im); | |
return FALSE; | |
} | |
if (fRemovePdbPath && !removePdbPath(&im)) { | |
dprint("error while editing pdb path\n"); | |
::UnMapAndLoad(&im); | |
return FALSE; | |
} | |
// end image patches | |
// Checksum offset is same for IMAGE_OPTIONAL_HEADER32 and 64. | |
DWORD old_sum, new_sum; | |
PIMAGE_NT_HEADERS pimh = ::CheckSumMappedFile(im.MappedAddress, im.SizeOfImage, &old_sum, &new_sum); | |
if (!pimh) { | |
dprint("err CheckSumMappedFile %d\n", GetLastError()); | |
::UnMapAndLoad(&im); | |
return FALSE; | |
} | |
// d3print( "old cksm=%4.4X new=%4.4X\n", old_sum, new_sum ); | |
pimh->OptionalHeader.CheckSum = new_sum; | |
r = ::UnMapAndLoad(&im); | |
if (!r) { | |
dprint("err writing file back after patching %d\n", GetLastError()); | |
return FALSE; | |
} | |
return TRUE; | |
} | |
#if UNICODE | |
PSTR strFilePathDeUnicode(PCWSTR tstr); | |
//--------------------------------------------------------------------------------- | |
// Only a non-unicode form of MapAndLoad exists in imagehlp. Grr. | |
//--------------------------------------------------------------------------------- | |
BOOL | |
MapAndLoadW( | |
__in PCWSTR fname, | |
__in_opt PCWSTR path, | |
__out PLOADED_IMAGE LoadedImage, | |
BOOL DotDll, | |
BOOL Readonly) | |
{ | |
BOOL r; | |
UNREFERENCED_PARAMETER(path); // fake this arg to not search, and not deunicode | |
// Convert PWSTR to something acceptable for CreateFileA | |
// Another way: Require the file *name* only be ascii, then path can be gibberish. | |
// => cd to the path and use ./filename form. | |
PSTR a_fname = strFilePathDeUnicode(fname); | |
if (!a_fname) { | |
dprint("error opening file for rechecksum (unicode path)\n"); | |
return FALSE; | |
} | |
r = MapAndLoad(a_fname, "\\dont-search-path", LoadedImage, DotDll, Readonly); | |
free(a_fname); | |
return r; | |
} | |
//--------------------------------------------------------------------------------- | |
// Convert unicode file path to something acceptable for non-unicode file APIs. | |
// Caller should free returned pointer with free() | |
//--------------------------------------------------------------------------------- | |
PSTR strFilePathDeUnicode(__in PCWSTR tstr) | |
{ | |
//- dprint("orig. path [%ws]\n", tstr ); | |
DWORD r; | |
PWSTR p = (PWSTR)calloc(MAX_PATH + 1, sizeof(WCHAR)); | |
if (!p) | |
return NULL; | |
for (int i = 0; i < MAX_PATH; i++) { | |
WCHAR w = tstr[i]; | |
if (!w) | |
return (PSTR)p; | |
if (w >(WCHAR)0xFF) | |
break; // go de-unucode | |
((char*)p)[i] = (char)(w & 0xFF); | |
} | |
r = ::GetShortPathNameW(tstr, p, MAX_PATH); | |
if (r == 0 || r >= MAX_PATH) { | |
dprint("err GetShortPathName, gle=%d\n", GetLastError()); | |
free(p); | |
return NULL; | |
} | |
// now convert to 1-byte string | |
for (int i = 0; i < MAX_PATH; i++) { | |
WCHAR w = p[i]; | |
if (!w) | |
break; | |
if (w >(WCHAR)0xFF) { | |
dprint("error: GetShortPathName returned non 0 code page??\n"); | |
free(p); | |
SetLastError(ERROR_INVALID_NAME); | |
return NULL; | |
} | |
p[i] = 0; | |
((char*)p)[i] = (char)(w & 0xFF); | |
} | |
d3print("de-unicoded name from [%ws]", tstr); | |
d3print("\nde-unicoded name => [%hs]\n", (PSTR)p); | |
return (PSTR)p; | |
} | |
#endif //UNICODE | |
int wmain(int argc, wchar_t *argv[]) | |
{ | |
wchar_t *fname = argv[1]; | |
if (!fname) { | |
printf("Usage: %ws FILENAME.sys\n", argv[0]); | |
return 1; | |
} | |
bool b = processFile(fname, false); | |
return b ? 0 : 1; | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Small utility to clear writable attribute on code sections in Windows drivers and other PE files.
Usage: wc-fixer <filename.sys>
The file will be patched in place - please make backups of precious files.
Of course, do this before signing the driver, or strip existing signatures off and then re-sign.