Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@pavel-a
Last active December 8, 2020 04:57
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pavel-a/090cbe4b2aea9a054c55974b7c7be634 to your computer and use it in GitHub Desktop.
Save pavel-a/090cbe4b2aea9a054c55974b7c7be634 to your computer and use it in GitHub Desktop.
Writable code section fixer
#pragma once
#define _WIN32_WINNT NTDDI_WINXP
#include <SDKDDKVer.h>
////////////////////////////////////////////////////////////////////////////////
// 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;
}
@pavel-a
Copy link
Author

pavel-a commented Sep 6, 2016

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment