Tuesday, June 11, 2024

A Workaround for "Levels not implemented for this platform"

The Windows kernel people may be familiar with a cool built-in "pte" extension that can help you troubleshoot ugly BSODs by checking if some kernel memory address is inaccessible.


0: kd> !pte ffff8d89`cce3efe8 
                                           VA ffff8d89cce3efe8
PXE at FFFF91C8E47238D8    PPE at FFFF91C8E471B138    PDE at FFFF91C8E3627338    PTE at FFFF91C6C4E671F0
contains 0A0000021E002863  contains 0A00000002903863  contains 0A0000012845B863  contains 8A0000015AD4F963
pfn 21e002    ---DA--KWEV  pfn 2903      ---DA--KWEV  pfn 12845b    ---DA--KWEV  pfn 15ad4f    -G-DA--KW-V

0: kd> dt nt!_HARDWARE_PTE FFFF91C6C4E671F0
   +0x000 Valid            : 0y1
   +0x000 Write            : 0y1
   +0x000 Owner            : 0y0
   +0x000 WriteThrough     : 0y0
   +0x000 CacheDisable     : 0y0
   +0x000 Accessed         : 0y1
   +0x000 Dirty            : 0y1
   +0x000 LargePage        : 0y0
   +0x000 Global           : 0y1
   +0x000 CopyOnWrite      : 0y0
   +0x000 Prototype        : 0y0
   +0x000 reserved0        : 0y1
   +0x000 PageFrameNumber  : 0y000000000000000101011010110101001111 (0x15ad4f)
   +0x000 reserved1        : 0y0000
   +0x000 SoftwareWsIndex  : 0y00010100000 (0xa0)
   +0x000 NoExecute        : 0y1


Normally it shows PTE address among with other information, but unfortunately it has been broken for a quite some time (and his colleague vtop - too):


0: kd> !pte fffff807`1f8a7c58
Levels not implemented for this platform


And it doesn't look like MS is going to fix it anytime soon, see:

https://learn.microsoft.com/en-us/answers/questions/1010386/windbg-pte-command-levels-not-implemented-for-this?page=1#answer-1315958

https://github.com/microsoftfeedback/WinDbg-Feedback/issues/8


It's no problem if you have a single occasional BSOD, but if you really need to use this extension for a number of dumps in a row it's getting annoying.

Sooo, I decided to use my small and nice (and a little bit outdated) code emulator and emulate nt!MiGetPteAddress instead:


0: kd> .load c:\orthia\orthia.dll;!orthia.profile /f %temp%\test.db; !orthia.vm_vm_def
0: kd> !orthia.vm_vm_call 0 nt!MiGetPteAddress --print rcx=fffff807`1f8a7c58

Diana Error Code: DI_END
rax=fffff6fc038fc538 rbx=fffff8071b881180 rcx=0000007c038fc538
rdx=0000025800000000 rsi=0000000000000001 rdi=000000000000000d
rip=0000000000000000 rsp=fffff8071f8a7c58 rbp=ffff940947d3e040
r8=0000000000000018  r9=ffff9409476e0000 r10=000000000000000b
r11=0000000000009ee6 r12=0000000000000000 r13=fffff8071b881180
r14=0000000000000001 r15=ffffffffffffff00
cs=0010  ss=0018  ds=002b  es=002b  fs=0053  gs=002b  efl=00000282
Commands count: 6
Modified pages:
Done

0: kd> dt ntkrnlmp!_HARDWARE_PTE fffff6fc038fc538
 +0x000 Valid            : 0y1
 +0x000 Write            : 0y1
 +0x000 Owner            : 0y0
 +0x000 WriteThrough     : 0y0
   +0x000 CacheDisable     : 0y0
   +0x000 Accessed         : 0y1
 +0x000 Dirty            : 0y1
   +0x000 LargePage        : 0y0
 +0x000 Global           : 0y0
 +0x000 CopyOnWrite      : 0y0
 +0x000 Prototype        : 0y0
 +0x000 reserved0        : 0y1
 +0x000 PageFrameNumber  : 0y000000000000000000000110110010100111 (0x6ca7)
 +0x000 reserved1        : 0y0000
 +0x000 SoftwareWsIndex  : 0y00010010000 (0x90)
 +0x000 NoExecute        : 0y1

As you can see there were just 6 assembler instruction to emulate, so it handled them successfully.

The extension is here: https://github.com/ligen-ua/diana-dasm/releases/tag/v1.1-tools

The sources and this case study is there:

https://github.com/ligen-ua/diana-dasm?tab=readme-ov-file#pte-case-study


Wednesday, November 23, 2022

ImageMagick sandboxing issue

And you know what? Of course I introduced a bug while implementing sandboxing feature for Fancy Viewer: the support of some raw images was broken from 1.0.2.10 till 1.0.2.13, because of Image Magick requires the use of a filesystem to load these raw images (actually their codecs just doesn't support Blobs).

The root cause of the bug is that Low Integrity application obviously can't access any general-purpose temporary folder; there are some folders that Image Magick knows about, and FOLDERID_LocalAppDataLow is not in the list:

 // ImageMagick-Windows\ImageMagick\MagickCore\resource.c
MagickExport MagickBooleanType GetPathTemplate(char *path)
{
............. (void) FormatLocaleString(path,MagickPathExtent,"magick-" MagickPathTemplate);
  exception=AcquireExceptionInfo();
  directory=(char *) GetImageRegistry(StringRegistryType,"temporary-path",
    exception);
  exception=DestroyExceptionInfo(exception);
  if (directory == (char *) NULL)
    directory=GetEnvironmentValue("MAGICK_TEMPORARY_PATH");
  if (directory == (char *) NULL)
    directory=GetEnvironmentValue("MAGICK_TMPDIR");
  if (directory == (char *) NULL)
    directory=GetEnvironmentValue("TMPDIR");
#if defined(MAGICKCORE_WINDOWS_SUPPORT) || defined(__OS2__) || defined(__CYGWIN__)
  if (directory == (char *) NULL)
    directory=GetEnvironmentValue("TMP");
  if (directory == (char *) NULL)
    directory=GetEnvironmentValue("TEMP");
#endif
#if defined(__VMS)
  if (directory == (char *) NULL)
    directory=GetEnvironmentValue("MTMPDIR");
#endif
#if defined(P_tmpdir)
  if (directory == (char *) NULL)
    directory=ConstantString(P_tmpdir);
#endif
.............
#endif
  return(MagickTrue);
} 

So, I set MAGICK_TEMPORARY_PATH environment variable pointing to some sub-directory of LocalLow folder (acquired by SHGetKnownFolderPath(..FOLDERID_LocalAppDataLow,,), and decided that the fix is done, a trivial one.

Except I found that nothing has changed.
That's why:
1) I set environment variable with SetEnvironmentVariable WinAPI function
2) ImageMagick uses getenv CRT function, which is part of UCRT on Windows

And Windows UCRT keeps own copy of all process environment variables without any synchronization with process environment block: it just copies all the variables while initialization and then uses a separate data structure that clearly resembles Unix'es "environ", except it is properly synchronized and has a longer name:

 // Windows Kits\10\Source\10.0.10240.0\ucrt\env\getenv.cpp


// These functions search the environment for a variable with the given name.
// If such a variable is found, a pointer to its value is returned.  Otherwise,
// nullptr is returned.  Note that if the environment is access and manipulated
// from multiple threads, this function cannot be safely used:  the returned
// pointer may not be valid when the function returns.
template <typename Character>
static Character* __cdecl common_getenv_nolock(Character const* const name) throw()
{
    typedef __crt_char_traits<Character> traits;
    
    Character** const environment = traits::get_or_create_environment_nolock();
    if (environment == nullptr || name == nullptr)
        return nullptr;

    size_t const name_length = traits::tcslen(name);

    for (Character** current = environment; *current; ++current)
    {
        if (traits::tcslen(*current) <= name_length)
            continue;

        if (*(*current + name_length) != '=')
            continue;

        if (traits::tcsnicoll(*current, name, name_length) != 0)
            continue;

        // Internal consistency check:  The environment string should never use
        // a bigger buffer than _MAX_ENV.  See also the SetEnvironmentVariable
        // SDK function.
        _ASSERTE(traits::tcsnlen(*current + name_length + 1, _MAX_ENV) < _MAX_ENV);

        return *current + name_length + 1;
    }

    return nullptr;
}

 So,

 _putenv_s("MAGICK_TEMPORARY_PATH", fvMagickAppDataPath.c_str());

did the trick.

Sunday, November 13, 2022

UI Windows Sandboxing

What I love about programming is that system programming can strike you back even if you are writing a simple desktop UI tool. For example, if you want it to be more secure, as I do. (it is Fancy Viewer tool in my case https://www.fancy-viewer.com/)

The tool uses ImageMagick library (plus plugins) which I completely trust, but vulnerabilities happen and it is better to run the parsers in some isolated environment, i.e. sandbox. 

There are some Windows API functions for that:

CreateRestrictedToken
CreateProcessAsUserW
which work as charm:

Except you have to rewrite token's default DACL or the app refuses to start on Windows 7 (or on Windows 10 with "run as administrator").

Like here:

BOOL CreateProcessRestrictedW(LPWSTR lpCommandLine,
    LPSTARTUPINFOW lpStartupInfo,
    BOOL bInheritHandles,
    DWORD dwCreationFlags,
    LPVOID lpEnvironment,
    LPCWSTR lpCurrentDirectory,
    OUT LPPROCESS_INFORMATION lpProcessInformation)
{
    HANDLE hProcessToken = 0, hRestrictedToken = 0;

    if (!OpenProcessToken(GetCurrentProcess(),
        TOKEN_ALL_ACCESS | TOKEN_ASSIGN_PRIMARY | TOKEN_DUPLICATE | TOKEN_QUERY,
        &hProcessToken))
    {
        return FALSE;
    }
    lsvu::HandleGuard processTokenGuard(hProcessToken);

    // get current user sid
    std::vector<char> currentUserSID;
    if (QueryTokenSID_Silent(hProcessToken, &currentUserSID))
    {
        return FALSE;
    }

    // collect other system sids
    std::vector<char> adminSID;
    if (GetWellKnownSid_Silent(WinBuiltinAdministratorsSid, adminSID))
    {
        return FALSE;
    }
    std::vector<char> localSystemSID;
    if (GetWellKnownSid_Silent(WinLocalSystemSid, localSystemSID))
    {
        return FALSE;
    }

    SID_AND_ATTRIBUTES sidToDisable = { adminSID.data(), 0 };

    // create the restricted token
    if (!CreateRestrictedToken(hProcessToken,
        DISABLE_MAX_PRIVILEGE | LUA_TOKEN,
        1, &sidToDisable,
        0, 0,
        0, 0,
        &hRestrictedToken))
    {
        return FALSE;
    }
    lsvu::HandleGuard restrictedTokenGuard(hRestrictedToken);

    // Set the token to low integrity:
    TOKEN_MANDATORY_LABEL tokenLabel = { 0 };
    tokenLabel.Label.Attributes = SE_GROUP_INTEGRITY;
    if (!ConvertStringSidToSidW(L"S-1-16-4096", &tokenLabel.Label.Sid))
    {
        return FALSE;
    }
    {
        LocalGuard sidLabelGuard(tokenLabel.Label.Sid);
        if (!SetTokenInformation(hRestrictedToken,
            TokenIntegrityLevel,
            &tokenLabel,
            sizeof(tokenLabel) + GetLengthSid(tokenLabel.Label.Sid)))
        {
            return FALSE;
        }
    }

    // Create new DACL
    std::vector<char> dacl;
    if (CreateACL_Silent(dacl, currentUserSID, adminSID, localSystemSID))
    {
        return FALSE;
    }
    
    TOKEN_DEFAULT_DACL newDefaulDACL = { (PACL)dacl.data() };
    if (!SetTokenInformation(hRestrictedToken, TokenDefaultDacl, 
&newDefaulDACL, sizeof(newDefaulDACL))) { return FALSE; } // Create a new process using the restricted token BOOL result = CreateProcessAsUserW(hRestrictedToken, NULL, lpCommandLine, NULL, NULL, bInheritHandles, dwCreationFlags, (LPVOID)lpEnvironment, lpCurrentDirectory, lpStartupInfo, lpProcessInformation); return result; } 
 

It isn't a full compilable source, but demonstrates the idia well enough.

Wednesday, October 26, 2022

[offtop] UI and Guilty Pleasure

Suddenly started a desktop tool for file viewing purposes:

 https://www.fancy-viewer.com/ 

Yeaaah, just another photo viewing tool: Windows/API, ImageMagick-based, see About->Licenses for licenses.

There are couple of reasons why I did it:

- first, I need a hobby and I'm not in the mood for another low level hobby-security-project; had a lot of that stuff on the job;

- then, the process of testing i.e, going through gigabytes of photos on my hard drive calms me very well, which is good for the mental health. Funny thing, UI bugs don't frustrate me at all (I know a lot of people who hate them terribly);

- I use the tool by myself and I just like it this way: I like the ability of reviewing photos without being limited with fixed-sized thumbnails; I also use built-in FTPS client features, Tags and Favorites features, Dark Theme, etc.

It is still a little bit raw: doesn't have proper auto-update system and code signing (I ordered the cert, but the process is slow as hell). UI obviously lacks of RTL support; multi-language support is not that great, it currently just supports only two of languages: English and Ukrainian. 

P.S: Some UI controls were created completely from scratch, and it was a lot of fun with Win32 stuff which I also love (while 70%-80% of my regular job is about Linux/Unix systems currently)

Saturday, January 6, 2018

Orthia Windbg Extension 2.0.0.193 released


 
Orthia Windbg Extension 2.0.0.193 Released
Latest version:
https://sourceforge.net/projects/diana-dasm/files/orthia-2.0.0.193.zip/download

1. New Major Feature added: Code Emulator


How to use: 
// load extension and setup profile
.load orthia.dll
!orthia.profile %temp%\test.db

// rewrite default VM (another way is to use vm_vm_new)
!orthia.vm_vm_def

// run the function and show the result
!orthia.vm_vm_call 0 nt!PsGetCurrentProcess --print 

See Wiki for the details:
https://sourceforge.net/p/diana-dasm/wiki/Orthia%20Windbg%20plugin/ 

2. Code Analyzer improved

1. Memory consumption reduced
2. Performance of analyzing improved



The latest sources: 
https://sourceforge.net/p/diana-dasm/code/HEAD/tree/trunk/src/
 

Wednesday, January 4, 2017

Windbg: raw dumping of usermode memory

This script works fine for me and dumps all the MEM_COMMIT pages to the specified folder:

.foreach /ps 6 (place  {!address -o:1 /f:MEM_COMMIT}) {   .foreach /pS a /ps 100 (size  {!address place }) { .echo place size; .writemem c:\dir2export\prefix${place} place L?${size}; } }

How to use:
- change "c:\dir4data" to your directory name;
- change "prefix" to the appropriate file prefix or remove it at all;

Do not forget that Windbg doesn't understand multi-line scripts and !address extension works differently in the kernel-mode environment.

UPD. For some strange reason Windbg randomly shows can't access the memory error, but the next run of script completes successfully.

UPD2. Also the script is useful for full memory search:
// 8 bytes
.foreach /ps 6 (place  {!address -o:1 /f:MEM_COMMIT}) { .foreach /pS a /ps 100 (size  {!address place }) { s -q ${place} L?${size}/8 put-variable-here; } }
// 4 bytes
.foreach /ps 6 (place  {!address -o:1 /f:MEM_COMMIT}) { .foreach /pS a /ps 100 (size  {!address place }) { s -d ${place} L?${size}/4 put-variable-here; } }

etc

Tuesday, June 7, 2016

The terrible story about .NET Invoke

1.  There is something interesting about PostMessage
There is a limit of 10,000 posted messages per message queue. This limit should be sufficiently large. If your application exceeds the limit, it should be redesigned to avoid consuming so many system resources. To adjust this limit, modify the following registry key.
HKEY_LOCAL_MACHINE
   SOFTWARE
      Microsoft
         Windows NT
            CurrentVersion
               Windows
                  USERPostMessageLimit
The minimum acceptable value is 4000. 
https://msdn.microsoft.com/ru-ru/library/windows/desktop/ms644944%28v=vs.85%29.aspx

2.  And this explains why Invoke hangs when reaches the limit:

private object MarshaledInvoke(Control caller, 
                               Delegate method, 
                               object[] args, 
                               bool synchronous)
{
    int lpdwProcessId;
/// ....
/// skipped
/// ....
    if (flag)
        this.InvokeMarshaledCallbacks();
    else
    {
        UnsafeNativeMethods.PostMessage(new HandleRef(this, this.Handle), 
                                        threadCallbackMessage, 
                                        IntPtr.Zero, 
                                        IntPtr.Zero);
    }
    if (!synchronous)
        return entry;
    if (!entry.IsCompleted)
        this.WaitForWaitHandle(entry.AsyncWaitHandle); // <<<< OOPS
    if (entry.exception != null)
        throw entry.exception;
    return entry.retVal;
}

http://workblog.pilin.name/2007/04/control.html 

It's a scary, scary world