D
D
DWZ2020-04-23 10:59:11
C++ / C#
DWZ, 2020-04-23 10:59:11

Stack corruption around a variable - how to overcome?

There is Windows 7 SP1 Pro 64 bit RUS. Beneath it, Visual C++ 2017 compiled a 32-bit command-line program. EXE calls a function from a DLL.

EXE
int main(int argc, char *argv[])

// куча всяких printf();

#ifdef _WIN32
  char version[2500];

  if (getWindowsVersion(version))
    printf("Operating System: %s\n", version);
#endif

#if defined(__USE_LARGEFILE) && defined(__USE_LARGEFILE64)
  printf("\nLarge file available: %d offset\n", __USE_FILE_OFFSET64);
#endif
}


DLL
bool getWindowsVersion(char* version)
{
    int index = 0;
    OSVERSIONINFOEX osvi = {};

    osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);

    if (!GetVersionEx((OSVERSIONINFO*)&osvi))
        return false;

    if (osvi.dwPlatformId != VER_PLATFORM_WIN32_NT)
        return false;
// Windows Vista / Windows 7
    if (osvi.dwMajorVersion == 6 && osvi.dwMinorVersion <= 1)
    {
        if (osvi.wProductType == VER_NT_WORKSTATION)
        {
            if (osvi.dwMinorVersion == 1)
                index += sprintf(version + index, "Microsoft Windows 7");
            else
                index += sprintf(version + index, "Microsoft Windows Vista");

            uint32_t productType = 0;

            HMODULE hKernel = GetModuleHandle("KERNEL32.DLL");

            if (hKernel)
            {
                typedef bool (__stdcall *funcGetProductInfo)(uint32_t, uint32_t, uint32_t, uint32_t, uint32_t*);
                funcGetProductInfo pGetProductInfo = (funcGetProductInfo)GetProcAddress(hKernel, "GetProductInfo");

                if (pGetProductInfo)
                    pGetProductInfo(6, 0, 0, 0, &productType);

                switch (productType)
                {
                case PRODUCT_STARTER:
                {
                    index += sprintf(version + index, " Starter");
                    break;
                }
                case PRODUCT_HOME_BASIC_N:
                {
                    index += sprintf(version + index, " Home Basic N");
                    break;
                }
                case PRODUCT_HOME_BASIC:
                {
                    index += sprintf(version + index, " Home Basic");
                    break;
                }
                case PRODUCT_HOME_PREMIUM:
                {
                    index += sprintf(version + index, " Home Premium");
                    break;
                }
                case PRODUCT_BUSINESS_N:
                {
                    index += sprintf(version + index, " Business N");
                    break;
                }
                case PRODUCT_BUSINESS:
                {
                    index += sprintf(version + index, " Business");
                    break;
                }
                case PRODUCT_ENTERPRISE:
                {
                    index += sprintf(version + index, " Enterprise");
                    break;
                }
                case PRODUCT_ULTIMATE:
                {
                    index += sprintf(version + index, " Ultimate");
                    break;
                }
                default:
                    break;
                }
            }
        }
        else if (osvi.wProductType == VER_NT_SERVER)
        {
            if (osvi.dwMinorVersion == 1)
                index += sprintf(version + index, "Microsoft Windows Server 2008 R2");
            else
                index += sprintf(version + index, "Microsoft Windows Server 2008");

            if (osvi.wSuiteMask & VER_SUITE_DATACENTER)
                index += sprintf(version + index, " Datacenter Edition");
            else if (osvi.wSuiteMask & VER_SUITE_ENTERPRISE)
                index += sprintf(version + index, " Enterprise Edition");
            else if (osvi.wSuiteMask == VER_SUITE_BLADE)
                index += sprintf(version + index, " Web Edition");
            else
                index += sprintf(version + index, " Standard Edition");
        }
    }
// Windows Server 2003
    else if (osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 2)
    {
        index += sprintf(version + index, "Microsoft Windows Server 2003");

        if (GetSystemMetrics(SM_SERVERR2))
            index += sprintf(version + index, " R2");

        if (osvi.wSuiteMask & VER_SUITE_DATACENTER)
            index += sprintf(version + index, " Datacenter Edition");
        else if (osvi.wSuiteMask & VER_SUITE_ENTERPRISE)
            index += sprintf(version + index, " Enterprise Edition");
        else if (osvi.wSuiteMask == VER_SUITE_BLADE)
            index += sprintf(version + index, " Web Edition");
        else
            index += sprintf(version + index, " Standard Edition");
    }
// Windows XP
    else if (osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 1)
    {
        index += sprintf(version + index, "Microsoft Windows XP");

        if (GetSystemMetrics(SM_MEDIACENTER))
            index += sprintf(version + index, " Media Center Edition");
        else if (GetSystemMetrics(SM_STARTER))
            index += sprintf(version + index, " Starter Edition");
        else if (GetSystemMetrics(SM_TABLETPC))
            index += sprintf(version + index, " Tablet PC Edition");
        else if (osvi.wSuiteMask & VER_SUITE_PERSONAL)
            index += sprintf(version + index, " Home Edition");
        else
            index += sprintf(version + index, " Professional");
    }
// Windows 2000
    else if (osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 0)
    {
        index += sprintf(version + index, "Microsoft Windows 2000");

        if (osvi.wProductType == VER_NT_WORKSTATION)
        {
            index += sprintf(version + index, " Professional");
        }
        else if (osvi.wProductType == VER_NT_SERVER)
        {
            if (osvi.wSuiteMask & VER_SUITE_DATACENTER)
                index += sprintf(version + index, " Datacenter Server");
            else if (osvi.wSuiteMask & VER_SUITE_ENTERPRISE)
                index += sprintf(version + index, " Advanced Server");
            else
                index += sprintf(version + index, " Server");
        }
    }
// Windows NT 4
    else if (osvi.dwMajorVersion == 4)
    {
        index += sprintf(version + index, "Microsoft Windows NT 4");

        if (osvi.wProductType == VER_NT_WORKSTATION)
        {
            index += sprintf(version + index, " Workstation");
        }
        else if (osvi.wProductType == VER_NT_SERVER)
        {
            if (osvi.wSuiteMask & VER_SUITE_ENTERPRISE)
                index += sprintf(version + index, " Server, Enterprise Edition");
            else
                index += sprintf(version + index, " Server");
        }
    }
    else
    {
        index += sprintf(version + index, "Microsoft Windows");
    }

// Service pack and full version info
    if (strlen(osvi.szCSDVersion) > 0)
    {
        index += sprintf(version + index, " %s", osvi.szCSDVersion);
    }

    index += sprintf(version + index, " (%d.%d.%d", osvi.dwMajorVersion, osvi.dwMinorVersion, osvi.dwBuildNumber & 0xFFFF);

// 64-bit Windows
#ifdef _WIN64
    index += sprintf(version + index, "; 64-bit");
#else
    bool isWow64 = false;
    HMODULE hKernel = GetModuleHandle("kernel32.dll");

    if (hKernel)
    {
        typedef bool (__stdcall *funcIsWow64Process)(void*, bool*);

        funcIsWow64Process pIsWow64Process = (funcIsWow64Process)GetProcAddress(hKernel, "IsWow64Process");

        if (pIsWow64Process)
        {
            pIsWow64Process(GetCurrentProcess(), &isWow64);
        }
    }

   if (isWow64)
        index += sprintf(version + index, "; 64-bit");
    else
        index += sprintf(version + index, "; 32-bit");
#endif

   index += sprintf(version + index, ")");

    return true;
}


On launch, we get an error Run-Time Check Failure #2 - Stack around the variable 'isWow64 was corrupted'

In my opinion, there is nothing suspicious in the sources.

If we comment out the call to getWindowsVersion() or several sprintfs, we get an exception An exception was thrown at 0x7740E136 (ntdll.dll) in avidemux_jobs.exe: 0xC0000005: Access violation while reading at address 0x00000044.

Increased the buffer size to 2500 - does not help, replaced sprintf with sprintf_s - does not help.
What to do, where to dig?

Answer the question

In order to leave comments, you need to log in

4 answer(s)
E
Evgeny Shatunov, 2020-04-24
@DWZ

The convention __stdcall obliges the function user to pass function parameters on the stack by value, but frees him to clean up the stack after the call. The stack of parameters is cleared by the called function itself.
As a result of changing the signature from BOOL (__stdcall *)(HANDLE, PBOOL)to, the bool (__stdcall *)(void*, bool*)compiler thinks about one stack size, and the function code thinks about others.
Therefore, the signatures and calling convention of an imported function must always match the declaration of this function in the imported library! Generally always! Even with the following text.
However, specifically here the problem we have is not a mismatch of types, because.
__stdcalltransfers its result through the register. register is integer or real. For an integer register, the type promotion rule is used. This means that the function, having written the value of the type BOOL(size 4B), will not spoil anything for the user code, which will read all 4B from the register, taking into account the promotion rule.
The real problem is that a pointer to a one-byte integer ( bool*) is passed into use as a pointer to a four-byte integer ( BOOL*or PBOOL). After all, the called function has a signature BOOL (__stdcall *)(HANDLE, PBOOL)and works with the second parameter as with a 4B integer by pointer.
This is what leads to damage to the stack and you, the author, are very lucky that you start in debugging, where each value on the stack is framed by a fence, the safety of which is always looked after by a special service code between calls to subroutines.
This is the fence next toisWow64and was broken as a result of a call IsWow64Processwith an inappropriate length parameter. Change the type isWow64to BOOLand everything will be fine, even the handle "kernel32.dll"can then be released normally.

A
Adamos, 2020-04-23
@Adamos

index += sprintf(...)
sprintf returns a negative number on error.

R
res2001, 2020-04-23
@res2001

In getWindowsVersion , pass the size of the buffer.
Replace spintf with snprintf and control the buffer overflow. Yes, snprintf can also return a negative value on error.
If snprintf is called with the first two parameters set to 0, it will calculate the required buffer size for your template and parameters and return it without really copying anything. You can use this opportunity.

A
Andy_U, 2020-04-25
@Andy_U

Well, if you add the missing include, and fix one line at the beginning of the function (causing a syntax error!) OSVERSIONINFOEX osvi = {};then everything works ... At least debug, at least release, at least x86, at least amd65. See a piece with corrections (if you combine everything into one file).
Used CLion and MS BuildTools. 2019. The Windows version is the same.

#include <stdio.h>
#include <stdbool.h>
#include <stdint.h>
#include <windows.h>

bool getWindowsVersion(char* version);

int main(int argc, char* argv[])
{
// куча всяких printf();

#ifdef _WIN32
    char version[2500];

    if (getWindowsVersion(version))
        printf("Operating System: %s\n", version);
#endif

#if defined(__USE_LARGEFILE) && defined(__USE_LARGEFILE64)
    printf("\nLarge file available: %d offset\n", __USE_FILE_OFFSET64);
#endif
}

bool getWindowsVersion(char* version)
{
    int index = 0;
    OSVERSIONINFOEX osvi;

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question