Automatically Attach API Monitor to New Processes

Generally, my go-to debugging technique is Time Travel Tracing (TTD.exe). However, without full Symbols for Windows, some types of analysis can be a little tedious creating scripts to dump the API parameters etc. In addition, while you can use breakpoint commands to dump API call parameters, the performance is extremely slow, especially with many breakpoints. So although it has not been updated in some time, API Monitor is till a fantastic tool to have on hand, with a very nice simple XML definition system to custom define APIs to monitor. However, as it has not been updated in so long the very useful process notification feature breaks in Windows 8.1/Server 2012 and later, due to an incompatibility with the process notification driver.

As a workaround, I made this simple tool that will wait for a process matching a filter, then automatically attach API monitor to it. You won’t capture the full start of the process, but it is much faster than manually trying to do so.

This is using UI automation which at times can be unreliable, in my current testing on Windows 11 for my use cases it seemed to work well enough.

To use:

  1. Start API Monitor ( http://www.rohitab.com/apimonitor#Download )
  2. Select the type of APIs you want to monitor.
  3. Launch the x86/x64 version depending on target exe you want to monitor, wild cards with * and ? are supported. If API monitor is elevated, this tool must be launched elevated; however if you are monitoring non-elevated EXE you should not need to run either tools elevated.

AutoAttachApiMon_x86.exe c*.exe

4. When processes are launched they should get detected and start being monitored. Note API Monitor can crash some processes, and may need to adjust API Monitor settings to handle those scenario

The code is available here:

https://github.com/chentiangemalc/AutoAttachApiMon

This can be built with Visual Studio 2022 with C++ / Windows SDK

How it works:

  • Finds the Window for API Monitor
  • Uses WMI query Select * From __InstanceCreationEvent Within 1 Where TargetInstance ISA ‘Win32_Process’ to notify when new processes start
  • When a new process starts the Process ID is identified
  • We save current foreground window
  • API monitor is brought to the foreground, the list of running processes in the API monitor Running Processes window is analyzed and searched for matching process ID
  • When found we make sure to scroll to that item, then simulate a right click, down key, and enter.
  • Restore the foreground window that was active before we did this

Use the x64 build with API Monitor x64 for 64-bit processes and Win32 build with API Monitor x86 for 32-bit processes.

This was written as a proof of concept / testing so may have flaws and is not fully tested, feel free to submit improvements via github.

AutoAttachApiMon.cpp

#include <windows.h>
#include <commctrl.h>
#include <iostream>
#include <vector>
#include <string>
#include <conio.h>

#include "ProcessCreatedEventDispatcher.h"

#pragma comment(lib, "user32.lib")
#pragma comment(lib, "comctl32.lib")

BOOL CALLBACK EnumChildProc(HWND hwnd, LPARAM lParam);
HWND FindChildWindowByClass(HWND parent, LPCWSTR className);
int GetListViewItemCount(HWND hwndListView);
int GetListViewColumnCount(HWND hwndListView);
std::wstring GetListViewItemText(HWND hwndListView, int itemIndex, int subItemIndex);
bool WildcardMatch(const std::wstring& str, const std::wstring& pattern);
bool EnableDebugPrivilege();
void BringWindowToForeground(HWND hWnd);
// Global variables
HWND g_hListView = nullptr;
HWND g_hwndMain = nullptr;
std::wstring processFilter;

int main()
{   
    LPWSTR commandLine = GetCommandLineW();
    int argc;
    LPWSTR* argv = CommandLineToArgvW(commandLine, &argc);

    if (argc < 2) {
        std::cout << "At least one argument is required." << std::endl;
        return 1; // Exit with error code 1
    }

    processFilter = std::wstring(argv[1]);
    LocalFree(argv);

    // needed to monitor when running elevated
    EnableDebugPrivilege();

    // Find the main window
    #ifdef _WIN64 // Check if building for 64-bit architecture
        g_hwndMain = FindWindow(NULL, L"Monitoring - API Monitor v2 64-bit (Administrator)");
        if (!g_hwndMain)
        {
            g_hwndMain = FindWindow(NULL, L"Monitoring - API Monitor v2 64-bit");
        }
    #else
        g_hwndMain = FindWindow(NULL, L"Monitoring - API Monitor v2 32-bit (Administrator)");
        if (!g_hwndMain)
        {
            g_hwndMain = FindWindow(NULL, L"Monitoring - API Monitor v2 32-bit");
        }
    #endif

    if (!g_hwndMain) {
        std::wcerr << L"API Monitor 64-bit not running!" << std::endl;
        return 1;
    }

    HWND hwndRunningProcesses = FindWindowEx(g_hwndMain, NULL, NULL, L"Running Processes");
    if (!hwndRunningProcesses)
    {
        std::wcerr << L"Running processes not found - make sure monitoring is on!" << std::endl;
        return 1;
    }

    g_hListView = FindChildWindowByClass(hwndRunningProcesses, L"SysListView32");


    if (!g_hListView) {
        std::wcerr << L"SysListView32 control not found" << std::endl;
        return 1;
    }

    

    // Extract table info

    int columnCount = GetListViewColumnCount(g_hListView);

    if (columnCount == 0)
    {
        std::wcout << L"Unable to detect any running processes in API Monitor. If API monitor is running as admin, make sure this is running as admin too." << std::endl;
        return 1;
    }
    
    std::wcout << L"Current running processes in API monitor ...";

    std::wcout << L"ColumnCount = " << columnCount << std::endl;

    int rowCount = GetListViewItemCount(g_hListView);
    std::wcout << L"RowCount = " << rowCount << std::endl;

    // Print table content
    for (int i = 0; i < rowCount; ++i) {
        for (int j = 0; j < columnCount; ++j) {
            std::wcout << GetListViewItemText(g_hListView, i, j) << L"\t";
        }
        std::wcout << std::endl;
    }

    ProcessCreatedEventDispatcher ProcessCreatedEventDispatcher{};
    ProcessCreatedEventDispatcher.NewProcessCreatedListeners.emplace_back([](auto processName, auto processId) {
        std::wcout << L"Process Name: " << processName << L" Process Id:" << processId << std::endl;
        if (WildcardMatch(processName, processFilter))
        {
            std::wcout << "monitoring!" << std::endl;
            ClickContextMenuItem(g_hListView, processId);
        }
        std::flush(std::cout);
        });

#ifdef _WIN64
    std::wcout << L"Waiting for 64-bit processes matching '" << processFilter << L"'" << std::endl;
#else
    std::wcout << L"Waiting for 32-bit processes matching '" << processFilter << L"'" << std::endl;
#endif
    // Wait for key press to exit the program
    std::cout << "Press any key to terminate" << std::endl;
    while (!_kbhit()) {}

    return 0;
}

void BringWindowToForeground(HWND hwnd) {
    if (!hwnd) {
        std::cerr << "Invalid window handle." << std::endl;
        return;

    }

    // Show the window if it is minimized
    if (IsIconic(hwnd)) {
        ShowWindow(hwnd, SW_RESTORE);
    }
    else {
        ShowWindow(hwnd, SW_SHOW);
    }

    // Bring the window to the foreground and set focus
    SetForegroundWindow(hwnd);
    SetFocus(hwnd);
}


void EnsureVisible(HWND hwndListView, int itemIndex) {

    SendMessage(hwndListView, LVM_ENSUREVISIBLE, (WPARAM)itemIndex, TRUE);

}


bool ClickContextMenuItem(HWND hwndListView, const std::wstring& matchText) {

    HWND hwndForeground = GetForegroundWindow();
   
    BringWindowToForeground(g_hwndMain);
    
    int itemCount = GetListViewItemCount(hwndListView);
    
    bool itemFound = false;
    
    int itemIndex = -1;


    for (int i = 0; i < itemCount; ++i) {
        std::wstring itemText = GetListViewItemText(hwndListView, i, 1);  // 2nd column is index 1
        if (itemText == matchText) {
            itemIndex = i;
            itemFound = true;
            break;
        }
    }

    if (!itemFound) {
        std::wcerr << L"No matching item found." << std::endl;
        return false;
    }

    // Select the found item
    // Ensure the item is visible
    EnsureVisible(hwndListView, itemIndex);

    // Get the process ID of the target process
    DWORD processId;
    GetWindowThreadProcessId(hwndListView, &processId);

    // Open the target process
    HANDLE hProcess = OpenProcess(PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE, FALSE, processId);
    if (!hProcess) {
        std::wcerr << L"Failed to open process. Error: " << GetLastError() << std::endl;
        return false;
    }

    // Allocate memory in the target process for the RECT structure
    RECT* pRemoteRect = (RECT*)VirtualAllocEx(hProcess, NULL, sizeof(RECT), MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
    if (!pRemoteRect) {
        std::wcerr << L"Failed to allocate memory in target process. Error: " << GetLastError() << std::endl;
        CloseHandle(hProcess);
        return false;
    }

    // Send the LVM_GETITEMRECT message to the ListView control
    SendMessage(hwndListView, LVM_GETITEMRECT, (WPARAM)itemIndex, (LPARAM)pRemoteRect);

    // Read the RECT structure from the target process
    RECT itemRect;
    if (!ReadProcessMemory(hProcess, pRemoteRect, &itemRect, sizeof(RECT), NULL)) {
        std::wcerr << L"Failed to read item rectangle from target process. Error: " << GetLastError() << std::endl;
        VirtualFreeEx(hProcess, pRemoteRect, 0, MEM_RELEASE);
        CloseHandle(hProcess);
        return false;
    }

    // Free the allocated memory in the target process
    VirtualFreeEx(hProcess, pRemoteRect, 0, MEM_RELEASE);
    CloseHandle(hProcess);
    // Calculate the middle point of the item rectangle
    POINT pt = {
        (itemRect.left + itemRect.right) / 2,
        (itemRect.top + itemRect.bottom) / 2
    };

    // Convert to screen coordinates
    ClientToScreen(hwndListView, &pt);

    // Set the cursor position to the item's center
    SetCursorPos(pt.x, pt.y);

    // Simulate mouse down and up to perform a left click
    INPUT inputs[7] = {};
    inputs[0].type = INPUT_MOUSE;
    inputs[0].mi.dwFlags = MOUSEEVENTF_RIGHTDOWN;

    inputs[1].type = INPUT_MOUSE;
    inputs[1].mi.dwFlags = MOUSEEVENTF_RIGHTUP;

    // DOWN arrow key down
    inputs[2].type = INPUT_KEYBOARD;
    inputs[2].ki.wVk = VK_DOWN;        // Virtual-key code for the DOWN arrow key
    inputs[2].ki.dwFlags = 0;          // Key down event

    // DOWN arrow key up
    inputs[3].type = INPUT_KEYBOARD;
    inputs[3].ki.wVk = VK_DOWN;        // Virtual-key code for the DOWN arrow key
    inputs[3].ki.dwFlags = KEYEVENTF_KEYUP; // Key up event

    // ENTER key down
    inputs[4].type = INPUT_KEYBOARD;
    inputs[4].ki.wVk = VK_RETURN;      // Virtual-key code for the ENTER key
    inputs[4].ki.dwFlags = 0;          // Key down event

    // ENTER key up
    inputs[5].type = INPUT_KEYBOARD;
    inputs[5].ki.wVk = VK_RETURN;      // Virtual-key code for the ENTER key
    inputs[5].ki.dwFlags = KEYEVENTF_KEYUP; // Key up event

    SendInput(6, inputs, sizeof(INPUT));

    BringWindowToForeground(hwndForeground);
    return true;
}

bool EnableDebugPrivilege()
{
    HANDLE hToken;

    // Open a handle to the access token for the calling process.
    if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken)) {
        std::cerr << "OpenProcessToken error: " << GetLastError() << std::endl;
        return false;
    }
    TOKEN_PRIVILEGES tp;
    LUID luid;

    if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &luid)) {
        std::cerr << "LookupPrivilegeValue error: " << GetLastError() << std::endl;
        return false;
    }

    tp.PrivilegeCount = 1;
    tp.Privileges[0].Luid = luid;
    tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;

    // Adjust Token Privileges
    if (!AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), (PTOKEN_PRIVILEGES)NULL, (PDWORD)NULL)) {
        std::cerr << "AdjustTokenPrivileges error: " << GetLastError() << std::endl;
        return false;
    }

    if (GetLastError() == ERROR_NOT_ALL_ASSIGNED) {
        std::cerr << "Unable to enable debug privilege. This is expected if not running elevated." << std::endl;
        return false;
    }

    return true;
}

bool WildcardMatch(const std::wstring& str, const std::wstring& pattern) {
    if (pattern.empty()) return str.empty();
    if (pattern[0] == L'*') {
        return WildcardMatch(str, pattern.substr(1)) || (!str.empty() && WildcardMatch(str.substr(1), pattern));
    }
    else if (pattern[0] == L'?' || pattern[0] == str[0]) {
        return WildcardMatch(str.substr(1), pattern.substr(1));
    }
    else {
        return false;
    }
}


// Enumerate child windows and find the target window
BOOL CALLBACK EnumChildProc(HWND hwnd, LPARAM lParam)
{
    HWND* result = reinterpret_cast<HWND*>(lParam);
    wchar_t className[256];
    GetClassName(hwnd, className, sizeof(className) / sizeof(className[0]));

    if (wcscmp(className, L"SysListView32") == 0) {
        *result = hwnd;
        return FALSE;
    }
    return TRUE;
}

// Find child window by class name
HWND FindChildWindowByClass(HWND parent, LPCWSTR className)
{
    HWND result = nullptr;
    EnumChildWindows(parent, EnumChildProc, reinterpret_cast<LPARAM>(&result));
    return result;
}

// Get the number of items in the ListView
int GetListViewItemCount(HWND hwndListView)
{
    return static_cast<int>(SendMessage(hwndListView, LVM_GETITEMCOUNT, 0, 0));
}

// Get the number of columns in the ListView
int GetListViewColumnCount(HWND hwndListView)
{
    HWND header = (HWND)SendMessage(hwndListView, LVM_GETHEADER, 0, 0);
    return static_cast<int>(SendMessage(header, HDM_GETITEMCOUNT, 0, 0));
}

// Get the text of a specific item in the ListView
std::wstring GetListViewItemText(HWND hwndListView, int itemIndex, int subItemIndex)
{
    DWORD processId;
    GetWindowThreadProcessId(hwndListView, &processId);

    HANDLE hProcess = OpenProcess(PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE, FALSE, processId);
    if (!hProcess) {
        int err = GetLastError();
        std::wcerr << L"Failed to open process err#" << err << std::endl;
        return L"";
    }

    // Allocate memory in the target process
    SIZE_T bufferSize = 256 * sizeof(wchar_t);
    SIZE_T lvItemSize = sizeof(LVITEM);
    SIZE_T totalSize = lvItemSize + bufferSize;
    LPVOID pRemoteBuffer = VirtualAllocEx(hProcess, nullptr, totalSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
    if (!pRemoteBuffer) {
        std::wcerr << L"Failed to allocate memory in target process" << std::endl;
        CloseHandle(hProcess);
        return L"";
    }

    // Write the LVITEM structure to the target process memory
    LVITEM lvItem = { 0 };
    lvItem.mask = LVIF_TEXT;
    lvItem.iItem = itemIndex;
    lvItem.iSubItem = subItemIndex;
    lvItem.pszText = (LPWSTR)((LPBYTE)pRemoteBuffer + lvItemSize);
    lvItem.cchTextMax = 256;

    if (!WriteProcessMemory(hProcess, pRemoteBuffer, &lvItem, lvItemSize, nullptr)) {
        std::wcerr << L"Failed to write LVITEM to target process memory" << std::endl;
        VirtualFreeEx(hProcess, pRemoteBuffer, 0, MEM_RELEASE);
        CloseHandle(hProcess);
        return L"";
    }

    // Send the LVM_GETITEMTEXT message to the ListView control
    SendMessage(hwndListView, LVM_GETITEMTEXT, itemIndex, (LPARAM)pRemoteBuffer);

    // Read the text back from the target process memory
    wchar_t buffer[256];
    if (!ReadProcessMemory(hProcess, (LPBYTE)pRemoteBuffer + lvItemSize, buffer, bufferSize, nullptr)) {
        std::wcerr << L"Failed to read item text from target process memory" << std::endl;
        VirtualFreeEx(hProcess, pRemoteBuffer, 0, MEM_RELEASE);
        CloseHandle(hProcess);
        return L"";
    }

    // Clean up
    VirtualFreeEx(hProcess, pRemoteBuffer, 0, MEM_RELEASE);
    CloseHandle(hProcess);

    return std::wstring(buffer);
}


ProcessCreatedEventDispatcher.h

#pragma once
#include <functional>
#include <vector>

#include <Wbemidl.h>
#include <wrl.h>
#include <string>
using namespace Microsoft::WRL;

class ProcessCreatedEventDispatcher : public IWbemObjectSink {

public:
    ProcessCreatedEventDispatcher();
    ~ProcessCreatedEventDispatcher();

    ULONG STDMETHODCALLTYPE AddRef() override;
    ULONG STDMETHODCALLTYPE Release() override;
    HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppv) override;
    HRESULT STDMETHODCALLTYPE Indicate(LONG lObjectCount, IWbemClassObject __RPC_FAR* __RPC_FAR* apObjArray) override;
    HRESULT STDMETHODCALLTYPE SetStatus(LONG lFlags, HRESULT hResult, BSTR strParam, IWbemClassObject __RPC_FAR* pObjParam) override;

    using NewProcessCreatedListener = void(std::wstring processName, std::wstring processId);

    std::vector<std::function<NewProcessCreatedListener>> NewProcessCreatedListeners{};
private:
    LONG m_lRef{};
    ComPtr<IWbemServices> pSvc{};
    ComPtr<IWbemLocator> pLoc{};
    ComPtr<IUnsecuredApartment> pUnsecApp{};
    ComPtr<IUnknown> pStubUnk{};
    ComPtr<IWbemObjectSink> pStubSink{};
};

ProcessCreatedEventDispatcher.cpp

#include "ProcessCreatedEventDispatcher.h"

# pragma comment(lib, "wbemuuid.lib")

#include <iostream>
#include <functional>
#include <string>
#include <vector>

#define _WIN32_DCOM
#include <Windows.h>
#include <comdef.h>
#include <Wbemidl.h>
#include <wrl.h>

using namespace std;
using namespace Microsoft::WRL;

ProcessCreatedEventDispatcher::ProcessCreatedEventDispatcher() {
    HRESULT hres;
    // Step 1: --------------------------------------------------
    // Initialize COM. ------------------------------------------

    hres = CoInitializeEx(0, COINIT_MULTITHREADED);
    if (FAILED(hres)) {
        cout << "Failed to initialize COM library. Error code = 0x" << hex << hres << endl;
        return; // Program has failed.
    }

    // Step 2: --------------------------------------------------
    // Set general COM security levels --------------------------
    // Note: If you are using Windows 2000, you need to specify -
    // the default authentication credentials for a user by using
    // a SOLE_AUTHENTICATION_LIST structure in the pAuthList ----
    // parameter of CoInitializeSecurity ------------------------

    hres = CoInitializeSecurity(NULL,
        -1, // COM negotiates service
        NULL, // Authentication services
        NULL, // Reserved
        RPC_C_AUTHN_LEVEL_DEFAULT, // Default authentication 
        RPC_C_IMP_LEVEL_IMPERSONATE, // Default Impersonation  
        NULL, // Authentication info
        EOAC_NONE, // Additional capabilities 
        NULL // Reserved
    );


    if (FAILED(hres)) {
        cout << "Failed to initialize security. Error code = 0x" << hex << hres << endl;
        CoUninitialize();
        return; // Program has failed.
    }

    // Step 3: ---------------------------------------------------
    // Obtain the initial locator to WMI -------------------------
    hres = CoCreateInstance(CLSID_WbemLocator, 0, CLSCTX_INPROC_SERVER, IID_IWbemLocator, (LPVOID*)pLoc.GetAddressOf());

    if (FAILED(hres)) {
        cout << "Failed to create IWbemLocator object. " << "Err code = 0x" << hex << hres << endl;
        CoUninitialize();
        return; // Program has failed.
    }

    // Step 4: ---------------------------------------------------
    // Connect to WMI through the IWbemLocator::ConnectServer method

    // Connect to the local root\cimv2 namespace
    // and obtain pointer pSvc to make IWbemServices calls.
    hres = pLoc->ConnectServer(_bstr_t(L"root\\CIMV2"),
        NULL,
        NULL,
        0,
        NULL,
        0,
        0,
        &pSvc
    );

    if (FAILED(hres)) {
        cout << "Could not connect. Error code = 0x" << hex << hres << endl;
        pLoc->Release();
        CoUninitialize();
        return; // Program has failed.
    }

    cout << "Connected to root\\CIMV2 WMI namespace" << endl;


    // Step 5: --------------------------------------------------
    // Set security levels on the proxy -------------------------

    hres = CoSetProxyBlanket(pSvc.Get(), // Indicates the proxy to set
        RPC_C_AUTHN_WINNT, // RPC_C_AUTHN_xxx 
        RPC_C_AUTHZ_NONE, // RPC_C_AUTHZ_xxx 
        NULL, // Server principal name 
        RPC_C_AUTHN_LEVEL_CALL, // RPC_C_AUTHN_LEVEL_xxx 
        RPC_C_IMP_LEVEL_IMPERSONATE, // RPC_C_IMP_LEVEL_xxx
        NULL, // client identity
        EOAC_NONE // proxy capabilities 
    );

    if (FAILED(hres)) {
        cout << "Could not set proxy blanket. Error code = 0x" << hex << hres << endl;
        pSvc->Release();
        pLoc->Release();
        CoUninitialize();
        return; // Program has failed.
    }

    // Step 6: -------------------------------------------------
    // Receive event notifications -----------------------------

    // Use an unsecured apartment for security
    hres = CoCreateInstance(CLSID_UnsecuredApartment, NULL, CLSCTX_LOCAL_SERVER, IID_IUnsecuredApartment, (void**)&pUnsecApp);

    this->ProcessCreatedEventDispatcher::AddRef();

    pUnsecApp->CreateObjectStub(this, &pStubUnk);

    pStubUnk->QueryInterface(IID_IWbemObjectSink, &pStubSink);

    _bstr_t WQL = L"Select * From __InstanceCreationEvent Within 1 "
        L"Where TargetInstance ISA 'Win32_Process' ";

    // The ExecNotificationQueryAsync method will call
    // The EventQuery::Indicate method when an event occurs
    hres = pSvc->ExecNotificationQueryAsync(_bstr_t("WQL"), WQL, WBEM_FLAG_SEND_STATUS, NULL, pStubSink.Get());

    // Check for errors.
    if (FAILED(hres)) {
        printf("ExecNotificationQueryAsync failed with = 0x%X\n", hres);
        pSvc->Release();
        pLoc->Release();
        pUnsecApp->Release();
        pStubUnk->Release();
        this->ProcessCreatedEventDispatcher::Release();
        pStubSink->Release();
        CoUninitialize();
        return;
    }
}

ProcessCreatedEventDispatcher::~ProcessCreatedEventDispatcher() {
    auto Result = pSvc->CancelAsyncCall(pStubSink.Get());
    pSvc->Release();
    pLoc->Release();
    pUnsecApp->Release();
    pStubUnk->Release();
    this->ProcessCreatedEventDispatcher::Release();
    pStubSink->Release();
    CoUninitialize();
}

ULONG ProcessCreatedEventDispatcher::AddRef() {
    return InterlockedIncrement(&m_lRef);
}

ULONG ProcessCreatedEventDispatcher::Release() {
    LONG lRef = InterlockedDecrement(&m_lRef);
    if (lRef == 0)
        delete this;
    return lRef;
}

HRESULT ProcessCreatedEventDispatcher::QueryInterface(REFIID riid, void** ppv) {
    if (riid == IID_IUnknown || riid == IID_IWbemObjectSink) {
        *ppv = (IWbemObjectSink*)this;
        AddRef();
        return WBEM_S_NO_ERROR;
    }
    else return E_NOINTERFACE;
}


HRESULT ProcessCreatedEventDispatcher::Indicate(long lObjectCount, IWbemClassObject** apObjArray) {
    HRESULT hr = S_OK;
    _variant_t vtProp;

    for (int i = 0; i < lObjectCount; i++) {
        hr = apObjArray[i]->Get(_bstr_t(L"TargetInstance"), 0, &vtProp, 0, 0);
        if (!FAILED(hr)) {
            ComPtr<IUnknown> pUnk = static_cast<IUnknown*>(vtProp);
            IWbemClassObject* pObj = nullptr;
            hr = pUnk->QueryInterface(IID_IWbemClassObject, reinterpret_cast<void**>(&pObj));
            if (SUCCEEDED(hr)) {
                _variant_t cn, pid, name;

                hr = pObj->Get(L"Handle", 0, &cn, NULL, NULL);
                hr = pObj->Get(L"ProcessId", 0, &pid, NULL, NULL);
                hr = pObj->Get(L"Name", 0, &name, NULL, NULL);

                if (SUCCEEDED(hr)) {
                    if ((cn.vt == VT_NULL) || (cn.vt == VT_EMPTY))
                        std::cout << "Handle : " << ((cn.vt == VT_NULL) ? "NULL" : "EMPTY") << endl;
                    else if ((cn.vt & VT_ARRAY))
                        std::cout << "Handle : " << "Array types not supported (yet)" << endl;
                    else {
                        std::wstring WideProcessHandle = std::wstring(cn.bstrVal);
                        std::wstring WideProcessId = std::to_wstring(pid.lVal);
                        std::wstring WideProcessName = std::wstring(name.bstrVal);
                        
                        // Pass the process ID, process name, and handle to the listener
                        for (auto& NewProcessCreatedListener : NewProcessCreatedListeners) {
                            NewProcessCreatedListener(WideProcessName, WideProcessId);
                        }
                    }
                }
                VariantClear(&cn);
                VariantClear(&pid);
                VariantClear(&name);
            }
            pObj->Release();
        }
        VariantClear(&vtProp);
    }

    return WBEM_S_NO_ERROR;
}

HRESULT ProcessCreatedEventDispatcher::SetStatus(
    /* [in] */ LONG lFlags,
    /* [in] */ HRESULT hResult,
    /* [in] */ BSTR strParam,
    /* [in] */ IWbemClassObject __RPC_FAR* pObjParam
) {
    if (lFlags == WBEM_STATUS_COMPLETE) {
        printf("Call complete. hResult = 0x%X\n", hResult);
    }
    else if (lFlags == WBEM_STATUS_PROGRESS) {
        printf("Call in progress.\n");
    }

    return WBEM_S_NO_ERROR;
} // end of EventSin

Posted in Uncategorized | 1 Comment

Strings Utility In PowerShell

Sometimes I’m working in environments where I can’t copy in any tools for troubleshooting and sometimes simply analyzing the strings in an .EXE gives many useful clues to how it works. Here is a simple version of strings utility that only relies on PowerShell.

The script: https://github.com/chentiangemalc/PowerShellScripts/blob/master/Extract-Strings.ps1

<#
.SYNOPSIS
Extracts strings from a file.

.DESCRIPTION
Extracts western printable character strings from a binary file, including ASCII strings and UTF16 strings of a minimum length.

.PARAMETER Path
Specifies the path to the file from which strings will be extracted.

.PARAMETER MinStringLength
Specifies the minimum length of strings to be extracted. The default value is 5.

.PARAMETER HideAsciiStrings
Specifies whether to hide ASCII strings. By default, ASCII strings are shown.

.PARAMETER HideUnicodeStrings
Specifies whether to hide Unicode strings. By default, Unicode strings are shown.

.EXAMPLE
Extract-Strings -Path "C:\Files\sample.exe"
Extracts strings from the file "c:\Files\Sample.exe".

Extract-Strings -Path "C:\Files\sample.exe" -HideUnicodeStrings

.INPUTS
None.

.OUTPUTS
Extracted strings are written to the pipeline.

.NOTES
Version: 1.0
Author: chentiangemalc

#>

[CmdletBinding()]
param (
    [Parameter(Mandatory=$true)]
    [ValidateScript({Test-Path $_ -PathType 'Leaf'})]
    [string]$Path,
    [int]$MinStringLength = 5,
    [switch]$HideAsciiStrings,
    [switch]$HideUnicodeStrings
)

$bytes = [System.IO.File]::ReadAllBytes($Path)
$currentASCIIstring = [System.Text.StringBuilder]::new()
$currentUNICODEstring = [System.Text.StringBuilder]::new()
    
for ($i = 0; $i -lt $bytes.Length; $i++) {
    if ($i + 1 -lt $bytes.Length) {
        if (($bytes[$i] -ge 0x20 -and $bytes[$i] -le 0x7E -or $bytes[$i] -eq 0x0D -or $bytes[$i] -eq 0x0A) -and $bytes[$i + 1] -eq 0x00) {
            [void]$currentUNICODEstring.Append([char]$bytes[$i])
        }
        elseif ($bytes[$i] -eq 0x00 -and $bytes[$i + 1] -eq 0x00) {
            if ($currentUNICODEstring.Length -ge $minStringLength) {
                if (!$HideUnicodeStrings)
                {
                    $currentUNICODEstring.ToString()
                }
            }

            [void]$currentUNICODEstring.Clear()
        }
    }

    if ($bytes[$i] -ge 0x20 -and $bytes[$i] -le 0x7E -or $bytes[$i] -eq 0x0D -or $bytes[$i] -eq 0x0A) {
       [void]$currentASCIIstring.Append([char]$bytes[$i])
    }
    elseif ($bytes[$i] -eq 0) {
        if ($currentASCIIstring.Length -ge $minStringLength) {
            if (!$HideAsciiStrings)
            {
                $currentASCIIstring.ToString()
            }
        }
        [void]$currentASCIIstring.Clear()
    }
    else {
        [void]$currentASCIIstring.Clear()
        [void]$currentUNICODEstring.Clear()
    }
}

Posted in Uncategorized | Leave a comment

Resolve an API Set Function Name to On Disk Module and Function or Offset

Here is a script I wrote experimenting with resolving API Set function calls to on disk module/function/offset.

<#
.SYNOPSIS
    This script resolves an API set and API name to a specific module and function name.

.DESCRIPTION
    This script uses the NativeMethods class to call Windows API functions for resolving
    a given API set and API name to the actual module and function name they correspond to.

.PARAMETER ApiSet
    The API set string to be resolved. For example: "api-ms-win-devices-query-l1-1-0".

.PARAMETER Api
    The API name string to be resolved. For example: "DevGetObjects".

.PARAMETER ShowOffset
    Will show the offset of function instead of function name.
    Use if symbols don't resolve.

.EXAMPLE
    .\Resolve-ApiSet.ps1 -ApiSet "api-ms-win-devices-query-l1-1-0" -Api "DevGetObjects"
    This command will resolve the given ApiSet and ApiName to their corresponding module and function name.

   .\Resolve-ApiSet.ps1 -ApiSet "api-ms-win-devices-query-l1-1-0" -Api "DevGetObjects" -ShowOffSet
    This command will resolve the given ApiSet and ApiName to their corresponding module and offset (based on the image base address on disk)


.NOTES
    Run as 32-bit PowerShell for 32-bit modules
    64-bit PowerShell for 64-bit modules.
    If names are not resolving you may need to configure
    Microsoft Symbol Server or place name of your desired
    Symbol server in 2nd param to SymInitialize
#>
param(
    [Parameter(Mandatory=$true)]
    [string]$ApiSet,
    [Parameter(Mandatory=$true)]
    [string]$Api,
    [switch]$ShowOffset
)

Add-Type -TypeDefinition @'
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;

public class NativeMethods
{
    [DllImport("dbghelp.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    private static extern bool SymInitialize(IntPtr hProcess, string UserSearchPath, bool fInvadeProcess);

    [DllImport("dbghelp.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    private static extern bool SymFromAddr(IntPtr hProcess, ulong Address, out ulong Displacement, IntPtr Symbol);

    [DllImport("dbghelp.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    private static extern bool SymCleanup(IntPtr hProcess);

    [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    private static extern IntPtr GetCurrentProcess();

    [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    private static extern IntPtr LoadLibrary(string lpFileName);

    [DllImport("kernel32.dll", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
    private static extern IntPtr GetProcAddress(IntPtr hModule, string procName);

    private const int MAX_SYM_NAME = 2000;

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    private struct SYMBOL_INFO
    {
        public uint SizeOfStruct;
        public uint TypeIndex;
        public ulong Reserved1;
        public ulong Reserved2;
        public uint Index;
        public uint Size;
        public ulong ModBase;
        public uint Flags;
        public ulong Value;
        public ulong Address;
        public uint Register;
        public uint Scope;
        public uint Tag;
        public uint NameLen;
        public uint MaxNameLen;
        public char Name;
    }

    public static void SymInitialize()
    {
        if (!SymInitialize(GetCurrentProcess(), "", true))
        {
            // ignore errors
            // can only be called once per process instance
            //Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
        }
    }

    public static string GetSymbolName(IntPtr address)
    {
        ulong displacement = 0;
        byte[] buffer = new byte[Marshal.SizeOf(typeof(SYMBOL_INFO)) + MAX_SYM_NAME * sizeof(char)];
        GCHandle bufferHandle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
        IntPtr symbolPtr = bufferHandle.AddrOfPinnedObject();
        SYMBOL_INFO symbol = (SYMBOL_INFO)Marshal.PtrToStructure(symbolPtr, typeof(SYMBOL_INFO));
        symbol.SizeOfStruct = (uint)Marshal.SizeOf(typeof(SYMBOL_INFO));
        symbol.MaxNameLen = MAX_SYM_NAME;
        
        Marshal.StructureToPtr(symbol, symbolPtr, true);
        if (SymFromAddr(GetCurrentProcess(), (ulong)address, out displacement, symbolPtr))
        {
            symbol = (SYMBOL_INFO)Marshal.PtrToStructure(symbolPtr, typeof(SYMBOL_INFO));
            string functionName = Marshal.PtrToStringUni(symbolPtr + 84);
            return functionName;
        }
        else
        {
            Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
        }

        //SymCleanup(GetCurrentProcess());
        bufferHandle.Free();

        return String.Empty;
    }

    [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern uint GetModuleFileName(
        IntPtr hModule,
        [MarshalAs(UnmanagedType.LPTStr)] StringBuilder lpFilename,
        uint nSize
    );

    public static string GetModuleFileName(IntPtr hModule)
    {
        uint bufferSize = 1024;
        StringBuilder fileNameBuilder = new StringBuilder((int)bufferSize);
        uint result = GetModuleFileName(hModule, fileNameBuilder, bufferSize);

        if (result == 0)
        {
            throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error());
        }
        else if (result == bufferSize)
        {
            throw new InvalidOperationException("The file name is too long. Increase the buffer size and try again.");
        }

        return fileNameBuilder.ToString();
    }


    [DllImport("ntdll.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    public static extern int LdrLoadDll(
        string DllPath,
        uint[] DllCharacteristics,
        ref UNICODE_STRING DllName,
        out IntPtr DllHandle
    );

    [DllImport("ntdll.dll", SetLastError = true, CallingConvention = CallingConvention.Cdecl)]
    public static extern int LdrUnloadDll(IntPtr ModuleHandle);

    [DllImport("ntdll.dll", CharSet = CharSet.Ansi, SetLastError = true)]
    public static extern int LdrGetProcedureAddress(
        IntPtr DllHandle,
        ref ANSI_STRING ProcedureName,
        uint ProcedureNumber,
        out IntPtr ProcedureAddress
    );

    [StructLayout(LayoutKind.Sequential)]
    public struct UNICODE_STRING
    {
        public ushort Length;
        public ushort MaximumLength;
        [MarshalAs(UnmanagedType.LPWStr)]
        public string Buffer;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct ANSI_STRING
    {
        public ushort Length;
        public ushort MaximumLength;
        [MarshalAs(UnmanagedType.LPStr)]
        public string Buffer;
    }

    public static UNICODE_STRING CreateUnicodeString(string str)
    {
        return new UNICODE_STRING
        {
            Length = (ushort)(str.Length * 2),
            MaximumLength = (ushort)((str.Length * 2) + 2),
            Buffer = str
        };
    }

    public static ANSI_STRING CreateAnsiString(string str)
    {
        return new ANSI_STRING
        {
            Length = (ushort)str.Length,
            MaximumLength = (ushort)(str.Length + 1),
            Buffer = str
        };
    }
}
'@

[Uint16]$IMAGE_DOS_SIGNATURE = 0x5A4D
[Uint16]$IMAGE_NT_OPTIONAL_HDR32_MAGIC = 0x10b
[UInt16]$IMAGE_NT_OPTIONAL_HDR64_MAGIC = 0x20b
[UInt16]$IMAGE_ROM_OPTIONAL_HDR_MAGIC = 0x107
[Uint16]$IMAGE_NUMBEROF_DIRECTORY_ENTRIES = 16
[Uint16]$IMAGE_SIZEOF_SHORT_NAME = 8
[Uint16]$IMAGE_SIZEOF_SECTION_HEADER = 40
 
enum IMAGE_FILE_MACHINE {
    IMAGE_FILE_RELOCS_STRIPPED           = 0x0001  # Relocation info stripped from file.
    IMAGE_FILE_EXECUTABLE_IMAGE          = 0x0002  # File is executable  (i.e. no unresolved external references).
    IMAGE_FILE_LINE_NUMS_STRIPPED        = 0x0004  # Line nunbers stripped from file.
    IMAGE_FILE_LOCAL_SYMS_STRIPPED       = 0x0008  # Local symbols stripped from file.
    IMAGE_FILE_AGGRESIVE_WS_TRIM         = 0x0010  # Aggressively trim working set
    IMAGE_FILE_LARGE_ADDRESS_AWARE       = 0x0020  # App can handle >2gb addresses
    IMAGE_FILE_BYTES_REVERSED_LO         = 0x0080  # Bytes of machine word are reversed.
    IMAGE_FILE_32BIT_MACHINE             = 0x0100  # 32 bit word machine.
    IMAGE_FILE_DEBUG_STRIPPED            = 0x0200  # Debugging info stripped from file in .DBG file
    IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP   = 0x0400  # If Image is on removable media, copy and run from the swap file.
    IMAGE_FILE_NET_RUN_FROM_SWAP         = 0x0800  # If Image is on Net, copy and run from the swap file.
    IMAGE_FILE_SYSTEM                    = 0x1000  # System File.
    IMAGE_FILE_DLL                       = 0x2000  # File is a DLL.
    IMAGE_FILE_UP_SYSTEM_ONLY            = 0x4000  # File should only be run on a UP machine
    IMAGE_FILE_BYTES_REVERSED_HI         = 0x8000  # Bytes of machine word are reversed.
    IMAGE_FILE_MACHINE_UNKNOWN           = 0x0
    IMAGE_FILE_MACHINE_TARGET_HOST       = 0x0001  # Useful for indicating we want to interact with the host and not a WoW guest.
    IMAGE_FILE_MACHINE_I386              = 0x014c  # Intel 386.
    IMAGE_FILE_MACHINE_R3000             = 0x0162  # MIPS little-endian, = 0x160 big-endian
    IMAGE_FILE_MACHINE_R4000             = 0x0166  # MIPS little-endian
    IMAGE_FILE_MACHINE_R10000            = 0x0168  # MIPS little-endian
    IMAGE_FILE_MACHINE_WCEMIPSV2         = 0x0169  # MIPS little-endian WCE v2
    IMAGE_FILE_MACHINE_ALPHA             = 0x0184  # Alpha_AXP
    IMAGE_FILE_MACHINE_SH3               = 0x01a2  # SH3 little-endian
    IMAGE_FILE_MACHINE_SH3DSP            = 0x01a3
    IMAGE_FILE_MACHINE_SH3E              = 0x01a4  # SH3E little-endian
    IMAGE_FILE_MACHINE_SH4               = 0x01a6  # SH4 little-endian
    IMAGE_FILE_MACHINE_SH5               = 0x01a8  # SH5
    IMAGE_FILE_MACHINE_ARM               = 0x01c0  # ARM Little-Endian
    IMAGE_FILE_MACHINE_THUMB             = 0x01c2  # ARM Thumb/Thumb-2 Little-Endian
    IMAGE_FILE_MACHINE_ARMNT             = 0x01c4  # ARM Thumb-2 Little-Endian
    IMAGE_FILE_MACHINE_AM33              = 0x01d3
    IMAGE_FILE_MACHINE_POWERPC           = 0x01F0  # IBM PowerPC Little-Endian
    IMAGE_FILE_MACHINE_POWERPCFP         = 0x01f1
    IMAGE_FILE_MACHINE_IA64              = 0x0200  # Intel 64
    IMAGE_FILE_MACHINE_MIPS16            = 0x0266  # MIPS
    IMAGE_FILE_MACHINE_ALPHA64           = 0x0284  # ALPHA64
    IMAGE_FILE_MACHINE_MIPSFPU           = 0x0366  # MIPS
    IMAGE_FILE_MACHINE_MIPSFPU16         = 0x0466  # MIPS
    IMAGE_FILE_MACHINE_AXP64             = 0x0284  # IMAGE_FILE_MACHINE_ALPHA64
    IMAGE_FILE_MACHINE_TRICORE           = 0x0520  # Infineon
    IMAGE_FILE_MACHINE_CEF               = 0x0CEF
    IMAGE_FILE_MACHINE_EBC               = 0x0EBC  # EFI Byte Code
    IMAGE_FILE_MACHINE_AMD64             = 0x8664  # AMD64 (K8)
    IMAGE_FILE_MACHINE_M32R              = 0x9041  # M32R little-endian
    IMAGE_FILE_MACHINE_ARM64             = 0xAA64  # ARM64 Little-Endian
    IMAGE_FILE_MACHINE_CEE               = 0xC0EE
}
 
[Flags()] enum IMAGE_SUBSYSTEM
{
    IMAGE_SUBSYSTEM_UNKNOWN              = 0   # Unknown subsystem.
    IMAGE_SUBSYSTEM_NATIVE               = 1   # Image doesnt require a subsystem.
    IMAGE_SUBSYSTEM_WINDOWS_GUI          = 2   # Image runs in the Windows GUI subsystem.
    IMAGE_SUBSYSTEM_WINDOWS_CUI          = 3   # Image runs in the Windows character subsystem.
    IMAGE_SUBSYSTEM_OS2_CUI              = 5   # image runs in the OS/2 character subsystem.
    IMAGE_SUBSYSTEM_POSIX_CUI            = 7   # image runs in the Posix character subsystem.
    IMAGE_SUBSYSTEM_NATIVE_WINDOWS       = 8   # image is a native Win9x driver.
    IMAGE_SUBSYSTEM_WINDOWS_CE_GUI       = 9   # Image runs in the Windows CE subsystem.
    IMAGE_SUBSYSTEM_EFI_APPLICATION      = 10  #
    IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER = 11   #
    IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER   = 12  #
    IMAGE_SUBSYSTEM_EFI_ROM              = 13
    IMAGE_SUBSYSTEM_XBOX                 = 14
    IMAGE_SUBSYSTEM_WINDOWS_BOOT_APPLICATION = 16
    IMAGE_SUBSYSTEM_XBOX_CODE_CATALOG    = 17
}
 
[Flags()] enum IMAGE_DLLCHARACTERISTICS {
    IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA = 0x0020  # Image can handle a high entropy 64-bit virtual address space.
    IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE = 0x0040     # DLL can move.
    IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY   = 0x0080     # Code Integrity Image
    IMAGE_DLLCHARACTERISTICS_NX_COMPAT    = 0x0100     # Image is NX compatible
    IMAGE_DLLCHARACTERISTICS_NO_ISOLATION = 0x0200     # Image understands isolation and doesn't want it
    IMAGE_DLLCHARACTERISTICS_NO_SEH       = 0x0400     # Image does not use SEH.  No SE handler may reside in this image
    IMAGE_DLLCHARACTERISTICS_NO_BIND      = 0x0800     # Do not bind this image.
    IMAGE_DLLCHARACTERISTICS_APPCONTAINER = 0x1000     # Image should execute in an AppContainer
    IMAGE_DLLCHARACTERISTICS_WDM_DRIVER   = 0x2000     # Driver uses WDM model
    IMAGE_DLLCHARACTERISTICS_GUARD_CF     = 0x4000     # Image supports Control Flow Guard.
    IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE     = 0x8000
}
 
enum IMAGE_DIRECTORY_ENTRY
{
    IMAGE_DIRECTORY_ENTRY_EXPORT          = 0   # Export Directory
    IMAGE_DIRECTORY_ENTRY_IMPORT          = 1   # Import Directory
    IMAGE_DIRECTORY_ENTRY_RESOURCE        = 2   # Resource Directory
    IMAGE_DIRECTORY_ENTRY_EXCEPTION       = 3   # Exception Directory
    IMAGE_DIRECTORY_ENTRY_SECURITY        = 4   # Security Directory
    IMAGE_DIRECTORY_ENTRY_BASERELOC       = 5   # Base Relocation Table
    IMAGE_DIRECTORY_ENTRY_DEBUG           = 6   # Debug Directory
    #     IMAGE_DIRECTORY_ENTRY_COPYRIGHT = 7   # (X86 usage)
    IMAGE_DIRECTORY_ENTRY_ARCHITECTURE    = 7   # Architecture Specific Data
    IMAGE_DIRECTORY_ENTRY_GLOBALPTR       = 8   # RVA of GP
    IMAGE_DIRECTORY_ENTRY_TLS             = 9   # TLS Directory
    IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG    = 10   # Load Configuration Directory
    IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT   = 11   # Bound Import Directory in headers
    IMAGE_DIRECTORY_ENTRY_IAT            = 12   # Import Address Table
    IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT   = 13   # Delay Load Import Descriptors
    IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR = 14   # COM Runtime descriptor
}
 
[Flags()] enum IMAGE_SCN {
#       IMAGE_SCN_TYPE_REG                   0x00000000  #  Reserved.
#       IMAGE_SCN_TYPE_DSECT                 0x00000001  #  Reserved.
#       IMAGE_SCN_TYPE_NOLOAD                0x00000002  #  Reserved.
#       IMAGE_SCN_TYPE_GROUP                 0x00000004  #  Reserved.
 IMAGE_SCN_TYPE_NO_PAD               = 0x00000008  #  Reserved.
#       IMAGE_SCN_TYPE_COPY                  0x00000010  #  Reserved.
 
 IMAGE_SCN_CNT_CODE                  = 0x00000020  #  Section contains code.
 IMAGE_SCN_CNT_INITIALIZED_DATA      = 0x00000040  #  Section contains initialized data.
 IMAGE_SCN_CNT_UNINITIALIZED_DATA    = 0x00000080  #  Section contains uninitialized data.
 
 IMAGE_SCN_LNK_OTHER                =  0x00000100  #  Reserved.
 IMAGE_SCN_LNK_INFO                 = 0x00000200  #  Section contains comments or some other type of information.
#       IMAGE_SCN_TYPE_OVER                  0x00000400  #  Reserved.
 IMAGE_SCN_LNK_REMOVE                = 0x00000800  #  Section contents will not become part of image.
 IMAGE_SCN_LNK_COMDAT                = 0x00001000  #  Section contents comdat.
#                                            0x00002000  #  Reserved.
#       IMAGE_SCN_MEM_PROTECTED - Obsolete =  0x00004000
 IMAGE_SCN_NO_DEFER_SPEC_EXC         = 0x00004000  #  Reset speculative exceptions handling bits in the TLB entries for this section.
 IMAGE_SCN_GPREL                     = 0x00008000  #  Section content can be accessed relative to GP
 IMAGE_SCN_MEM_FARDATA               = 0x00008000
#       IMAGE_SCN_MEM_SYSHEAP  - Obsolete    0x00010000
 IMAGE_SCN_MEM_PURGEABLE             = 0x00020000
 IMAGE_SCN_MEM_16BIT                 = 0x00020000
 IMAGE_SCN_MEM_LOCKED                = 0x00040000
 IMAGE_SCN_MEM_PRELOAD              =  0x00080000
 
 IMAGE_SCN_ALIGN_1BYTES             =  0x00100000  # 
 IMAGE_SCN_ALIGN_2BYTES             =  0x00200000  # 
 IMAGE_SCN_ALIGN_4BYTES             =  0x00300000  # 
 IMAGE_SCN_ALIGN_8BYTES             =  0x00400000  # 
 IMAGE_SCN_ALIGN_16BYTES            =  0x00500000  #  Default alignment if no others are specified.
 IMAGE_SCN_ALIGN_32BYTES            =  0x00600000  # 
 IMAGE_SCN_ALIGN_64BYTES            =  0x00700000  # 
 IMAGE_SCN_ALIGN_128BYTES           =  0x00800000  # 
 IMAGE_SCN_ALIGN_256BYTES           =  0x00900000  # 
 IMAGE_SCN_ALIGN_512BYTES           =  0x00A00000  # 
 IMAGE_SCN_ALIGN_1024BYTES          =  0x00B00000  # 
 IMAGE_SCN_ALIGN_2048BYTES          =  0x00C00000  # 
 IMAGE_SCN_ALIGN_4096BYTES          =  0x00D00000  # 
 IMAGE_SCN_ALIGN_8192BYTES          =  0x00E00000  # 
#  Unused                                    0x00F00000
 IMAGE_SCN_ALIGN_MASK               =  0x00F00000
 
 IMAGE_SCN_LNK_NRELOC_OVFL          =  0x01000000  #  Section contains extended relocations.
 IMAGE_SCN_MEM_DISCARDABLE          =  0x02000000  #  Section can be discarded.
 IMAGE_SCN_MEM_NOT_CACHED           =  0x04000000  #  Section is not cachable.
 IMAGE_SCN_MEM_NOT_PAGED            =  0x08000000  #  Section is not pageable.
 IMAGE_SCN_MEM_SHARED               =  0x10000000  #  Section is shareable.
 IMAGE_SCN_MEM_EXECUTE              =  0x20000000  #  Section is executable.
 IMAGE_SCN_MEM_READ                 =  0x40000000  #  Section is readable.
 IMAGE_SCN_MEM_WRITE                =  0x80000000  #  Section is writeable.
 
# 
#  TLS Characteristic Flags
# 
 IMAGE_SCN_SCALE_INDEX              =  0x00000001  #  Tls index is scaled
 
}
 
Function Convert-EnumToString([Enum]$enum)
{
    $type = [Type]$enum
}
$IMAGE_DOS_HEADER = New-Object System.Collections.Specialized.OrderedDictionary
 
$IMAGE_DOS_HEADER.Add("e_magic",[Uint16]0)                     # magic number
$IMAGE_DOS_HEADER.Add("e_cblp",[UInt16]0)                      # Bytes on last page of file
$IMAGE_DOS_HEADER.Add("e_cp",[UInt16]0)                        # Pages in file
$IMAGE_DOS_HEADER.Add("e_crlc",[UInt16]0)                      # Relocations
$IMAGE_DOS_HEADER.Add("e_cparhdr",[UInt16]0)                   # Size of header in paragraphs
$IMAGE_DOS_HEADER.Add("e_minalloc",[UInt16]0)                  # Minimum extra paragraphs needed
$IMAGE_DOS_HEADER.Add("e_maxalloc",[UInt16]0)                  # Maximum extra paragraphs needed
$IMAGE_DOS_HEADER.Add("e_ss",[UInt16]0)                        # Initial (relative) SS value
$IMAGE_DOS_HEADER.Add("e_sp",[UInt16]0)                        # Initial SP value
$IMAGE_DOS_HEADER.Add("e_csum",[UInt16]0)                      # Checksum
$IMAGE_DOS_HEADER.Add("e_ip",[UInt16]0)                        # Initial IP value
$IMAGE_DOS_HEADER.Add("e_cs",[UInt16]0)                        # Initial (relative) CS value
$IMAGE_DOS_HEADER.Add("e_lfarlc",[UInt16]0)                    # File address of relocation table
$IMAGE_DOS_HEADER.Add("e_ovno",[UInt16]0)                      # Overlay number
$IMAGE_DOS_HEADER.Add("e_res",(New-Object UInt16[] 4))         # Reserved words
$IMAGE_DOS_HEADER.Add("e_oemid",[UInt16]0)                     # OEM identifier (for e_oeminfo)
$IMAGE_DOS_HEADER.Add("e_oeminfo",[UInt16]0)                   # OEM information,[UInt16]0) e_oemid specific
$IMAGE_DOS_HEADER.Add("e_res2[10]",(New-Object UInt16[] 10))   # Reserved words
$IMAGE_DOS_HEADER.Add("e_lfanew",[Uint32]0)                    # File address of new exe header
 
$IMAGE_FILE_HEADER = New-Object System.Collections.Specialized.OrderedDictionary
$IMAGE_FILE_HEADER.Add("Machine",[UInt16]0)
$IMAGE_FILE_HEADER.Add("NumberOfSections",[UInt16]0)
$IMAGE_FILE_HEADER.Add("TimeDateStamp",[UInt32]0)
$IMAGE_FILE_HEADER.Add("PointerToSymbolTable",[UInt32]0)
$IMAGE_FILE_HEADER.Add("NumberOfSymbols",[UInt32]0)
$IMAGE_FILE_HEADER.Add("SizeOfOptionalHeader",[UInt16]0)
$IMAGE_FILE_HEADER.Add("Characteristics",[UInt16]0)
 
$IMAGE_OPTIONAL_HEADER = New-Object System.Collections.Specialized.OrderedDictionary
 
$IMAGE_OPTIONAL_HEADER.Add("Magic",[Uint16]0)
$IMAGE_OPTIONAL_HEADER.Add("MajorLinkerVersion",[Byte]0)
$IMAGE_OPTIONAL_HEADER.Add("MinorLinkerVersion",[Byte]0)
$IMAGE_OPTIONAL_HEADER.Add("SizeOfCode",[Uint32]0)
$IMAGE_OPTIONAL_HEADER.Add("SizeOfInitializedData",[Uint32]0)
$IMAGE_OPTIONAL_HEADER.Add("SizeOfUninitializedData",[Uint32]0)
$IMAGE_OPTIONAL_HEADER.Add("AddressOfEntryPoint",[Uint32]0)
$IMAGE_OPTIONAL_HEADER.Add("BaseOfCode",[Uint32]0)
$IMAGE_OPTIONAL_HEADER.Add("BaseOfData",[Uint32]0)
 
# NT Additional Fields
 
$IMAGE_OPTIONAL_HEADER.Add("ImageBase",[Uint32]0)
$IMAGE_OPTIONAL_HEADER.Add("SectionAlignment",[Uint32]0)
$IMAGE_OPTIONAL_HEADER.Add("FileAlignment",[Uint32]0)
$IMAGE_OPTIONAL_HEADER.Add("MajorOperatingSystemVersion",[Uint16]0)
$IMAGE_OPTIONAL_HEADER.Add("MinorOperatingSystemVersion",[Uint16]0)
$IMAGE_OPTIONAL_HEADER.Add("MajorImageVersion",[Uint16]0)
$IMAGE_OPTIONAL_HEADER.Add("MinorImageVersion",[Uint16]0)
$IMAGE_OPTIONAL_HEADER.Add("MajorSubsystemVersion",[Uint16]0)
$IMAGE_OPTIONAL_HEADER.Add("MinorSubsystemVersion",[Uint16]0)
$IMAGE_OPTIONAL_HEADER.Add("Win32VersionValue",[Uint32]0)
$IMAGE_OPTIONAL_HEADER.Add("SizeOfImage",[Uint32]0)
$IMAGE_OPTIONAL_HEADER.Add("SizeOfHeaders",[Uint32]0)
$IMAGE_OPTIONAL_HEADER.Add("CheckSum",[Uint32]0)
$IMAGE_OPTIONAL_HEADER.Add("Subsystem",[Uint16]0)
$IMAGE_OPTIONAL_HEADER.Add("DllCharacteristics",[Uint16]0)
$IMAGE_OPTIONAL_HEADER.Add("SizeOfStackReserve",[Uint32]0)
$IMAGE_OPTIONAL_HEADER.Add("SizeOfStackCommit",[Uint32]0)
$IMAGE_OPTIONAL_HEADER.Add("SizeOfHeapReserve",[Uint32]0)
$IMAGE_OPTIONAL_HEADER.Add("SizeOfHeapCommit",[Uint32]0)
$IMAGE_OPTIONAL_HEADER.Add("LoaderFlags",[Uint32]0)
$IMAGE_OPTIONAL_HEADER.Add("NumberOfRvaAndSizes",[Uint32]0)
$IMAGE_OPTIONAL_HEADER.Add("DataDirectory",(New-Object System.Collections.Specialized.OrderedDictionary[] $IMAGE_NUMBEROF_DIRECTORY_ENTRIES))
For ($i = 0; $i -lt $IMAGE_OPTIONAL_HEADER["DataDirectory"].Count;$i++)
{
    $IMAGE_OPTIONAL_HEADER["DataDirectory"][$i] = New-Object System.Collections.Specialized.OrderedDictionary
    $IMAGE_OPTIONAL_HEADER["DataDirectory"][$i].Add("VirtualAddress",[Uint32]0)
    $IMAGE_OPTIONAL_HEADER["DataDirectory"][$i].Add("Size",[UINt32]0)
}
 
 
$IMAGE_OPTIONAL_HEADER64 = New-Object System.Collections.Specialized.OrderedDictionary
$IMAGE_OPTIONAL_HEADER64.Add("Magic",[Uint16]0)
$IMAGE_OPTIONAL_HEADER64.Add("MajorLinkerVersion",[Byte]0)
$IMAGE_OPTIONAL_HEADER64.Add("MinorLinkerVersion",[Byte]0)
$IMAGE_OPTIONAL_HEADER64.Add("SizeOfCode",[Uint32]0)
$IMAGE_OPTIONAL_HEADER64.Add("SizeOfInitializedData",[Uint32]0)
$IMAGE_OPTIONAL_HEADER64.Add("SizeOfUninitializedData",[Uint32]0)
$IMAGE_OPTIONAL_HEADER64.Add("AddressOfEntryPoint",[Uint32]0)
$IMAGE_OPTIONAL_HEADER64.Add("BaseOfCode",[Uint32]0)
$IMAGE_OPTIONAL_HEADER64.Add("ImageBase",[Uint64]0)
$IMAGE_OPTIONAL_HEADER64.Add("SectionAlignment",[Uint32]0)
$IMAGE_OPTIONAL_HEADER64.Add("FileAlignment",[Uint32]0)
$IMAGE_OPTIONAL_HEADER64.Add("MajorOperatingSystemVersion",[Uint16]0)
$IMAGE_OPTIONAL_HEADER64.Add("MinorOperatingSystemVersion",[Uint16]0)
$IMAGE_OPTIONAL_HEADER64.Add("MajorImageVersion",[Uint16]0)
$IMAGE_OPTIONAL_HEADER64.Add("MinorImageVersion",[Uint16]0)
$IMAGE_OPTIONAL_HEADER64.Add("MajorSubsystemVersion",[Uint16]0)
$IMAGE_OPTIONAL_HEADER64.Add("MinorSubsystemVersion",[Uint16]0)
$IMAGE_OPTIONAL_HEADER64.Add("Win32VersionValue",[Uint32]0)
$IMAGE_OPTIONAL_HEADER64.Add("SizeOfImage",[Uint32]0)
$IMAGE_OPTIONAL_HEADER64.Add("SizeOfHeaders",[Uint32]0)
$IMAGE_OPTIONAL_HEADER64.Add("CheckSum",[Uint32]0)
$IMAGE_OPTIONAL_HEADER64.Add("Subsystem",[Uint16]0)
$IMAGE_OPTIONAL_HEADER64.Add("DllCharacteristics",[Uint16]0)
$IMAGE_OPTIONAL_HEADER64.Add("SizeOfStackReserve",[Uint64]0)
$IMAGE_OPTIONAL_HEADER64.Add("SizeOfStackCommit",[Uint64]0)
$IMAGE_OPTIONAL_HEADER64.Add("SizeOfHeapReserve",[Uint64]0)
$IMAGE_OPTIONAL_HEADER64.Add("SizeOfHeapCommit",[Uint64]0)
$IMAGE_OPTIONAL_HEADER64.Add("LoaderFlags",[Uint32]0)
$IMAGE_OPTIONAL_HEADER64.Add("NumberOfRvaAndSizes",[Uint32]0)
$IMAGE_OPTIONAL_HEADER64.Add("DataDirectory",(New-Object System.Collections.Specialized.OrderedDictionary[] $IMAGE_NUMBEROF_DIRECTORY_ENTRIES))
For ($i = 0; $i -lt $IMAGE_OPTIONAL_HEADER64["DataDirectory"].Count;$i++)
{
    $IMAGE_OPTIONAL_HEADER64["DataDirectory"][$i] = New-Object System.Collections.Specialized.OrderedDictionary
    $IMAGE_OPTIONAL_HEADER64["DataDirectory"][$i].Add("VirtualAddress",[Uint32]0)
    $IMAGE_OPTIONAL_HEADER64["DataDirectory"][$i].Add("Size",[UINt32]0)
}
 
$IMAGE_SECTION_HEADER = New-Object System.Collections.Specialized.OrderedDictionary
$IMAGE_SECTION_HEADER.Add("Name",(New-Object Byte[] $IMAGE_SIZEOF_SHORT_NAME))
$IMAGE_SECTION_HEADER.Add("Misc.PhysicalAddress",[Uint32]0)
$IMAGE_SECTION_HEADER.Add("Misc.VirtualSize",[Uint32]0)
$IMAGE_SECTION_HEADER.Add("VirtualAddress",[Uint32]0)
$IMAGE_SECTION_HEADER.Add("SizeOfRawData",[Uint32]0)
$IMAGE_SECTION_HEADER.Add("PointerToRawData",[Uint32]0)
$IMAGE_SECTION_HEADER.Add("PointerToRelocations",[Uint32]0)
$IMAGE_SECTION_HEADER.Add("PointerToLinenumbers",[Uint32]0)
$IMAGE_SECTION_HEADER.Add("NumberOfRelocations",[Uint16]0)
$IMAGE_SECTION_HEADER.Add("NumberOfLinenumbers",[Uint16]0)
$IMAGE_SECTION_HEADER.Add("Characteristics",[Uint32]0)
 
Function Convert-EnumToString
{
    param($enum,$value)
     
    $builder = New-Object System.Text.StringBuilder
    $names = ($enum.DeclaredFields | Select-Object -Property Name | Where-Object { $_.Name -ne "value__" }).Name
 
    ForEach ($name in $names)
    {
        if (!($value -ne 0 -and [Enum]::Parse($enum,$name).value__ -eq 0))
        {
            if ((([Enum]::Parse($enum,$name).value__) -band $value) -eq ([Enum]::Parse($enum,$name).value__))
            {
                if ($builder.Length -gt 0)
                {
                    [void]$builder.Append(" | ")
                }
                [void]$builder.Append($name)
            }
        }
    }
 
    return $builder.ToString()
     
}
Function Read-BinaryFile
{
    param(
    [System.IO.BinaryReader][ref]$reader,
    [System.Collections.Specialized.OrderedDictionary][ref]$items)
    
    
    $keys = New-Object String[] $items.Count
    $items.Keys.CopyTo($keys,0)
   
    for ($i = 0;$i -lt $items.Count;$i++)
    {
        $item = $keys[$i]
        
        if ($items[$item] -is [Array])
        {
            if ($items[$item][0] -is [System.Collections.Specialized.OrderedDictionary])
            {
                ForEach ($subItem in $items[$item])
                {
                    Read-BinaryFile -reader ([ref]$reader) -items ([ref]$subItem)
                }
            }
            else
            {
                $currentItem = $items[$item][0]
                For ($j = 0;$j -lt $items[$item].Length;$j++)
                {
                    switch($currentItem.GetType().Name)
                    {
                        "Byte"
                        {
                            $items[$item][$j] = $reader.ReadByte()
                        }
                        "Uint16"
                        { 
                            $items[$item][$j] = $Reader.ReadUint16()
                        }
                        "Uint32"
                        {
                            $items[$item][$j] = $reader.ReadUInt32()
                        }
                        "Uint64"
                        { 
                            $items[$item][$j] = $Reader.ReadUInt64()
                        }
                        default  { "Unknown Type! $($currentItem.GetType().Name)" }
                    }
                }
            }
        }
        else
        {
            $currentItem = $items[$item]
            if ($currentItem -is [System.Collections.Specialized.OrderedDictionary])
            {
                Read-BinaryFile -reader ([ref]$reader) -items ([ref]$items[$item])
            }
            else
            {
                switch($currentItem.GetType().Name)
                {
                    "Byte"
                    {
                        $items[$item] = $reader.ReadByte()
                    }
 
                    "Uint16" { 
                        $items[$item] = $reader.ReadUint16()
 
                    }
                    "Uint32"
                    { 
                         $items[$item] = $reader.ReadUint32()
                    }
                    "Uint64"
                    {
                        $items[$item] = $reader.ReadUint64()               
                    }
                    default  { "Unknown Type! $($currentItem.GetType().Name)" }
                }
            }
        }
    }
}

[NativeMethods]::SymInitialize()

$apiSetName = [NativeMethods]::CreateUnicodeString($ApiSet)
$apiName = [NativeMethods]::CreateAnsiString($Api)

[IntPtr]$dllHandle = [IntPtr]::Zero
[IntPtr]$procAddress = [IntPtr]::Zero

if ([NativeMethods]::LdrLoadDll("",0,[ref]$apiSetName,[ref]$dllHandle) -eq 0)
{
    if ([NativeMethods]::LdrGetProcedureAddress($dllHandle,[ref]$apiName,0,[ref]$procAddress) -eq 0)
    {
        if ($procAddress -ne [IntPtr]::Zero)
        {
            $filename = [NativeMethods]::GetModuleFileName($dllHandle)
            if ($ShowOffset)
            {
                $FileStream = [System.IO.File]::Open($filename, [System.IO.FileMode]::Open,[System.IO.FileAccess]::Read,[System.IO.FileShare]::Read)
 
                $BinaryReader = New-Object System.IO.BinaryReader($FileStream)
                [void]$BinaryReader.BaseStream.Seek(0,[System.IO.SeekOrigin]::Begin)
 
                Read-BinaryFile -reader ([ref]$BinaryReader) -Items ([ref]$IMAGE_DOS_HEADER)
 
                if ($IMAGE_DOS_HEADER["e_magic"] -eq $IMAGE_DOS_SIGNATURE)
                {
                    [void]$BinaryReader.BaseStream.Seek($IMAGE_DOS_HEADER["e_lfanew"] + 24,[System.IO.SeekOrigin]::Begin)
                    $magic = $BinaryReader.ReadInt16()
                    [void]$BinaryReader.BaseStream.Seek($IMAGE_DOS_HEADER["e_lfanew"],[System.IO.SeekOrigin]::Begin)
 
                    $IMAGE_NT_HEADER = New-Object System.Collections.Specialized.OrderedDictionary
                    $IMAGE_NT_HEADER.Add("Signature",[Uint32]0)
                    $IMAGE_NT_HEADER.Add("FileHeader",$IMAGE_FILE_HEADER)
 
                    switch ($magic)
                    {
                        $IMAGE_NT_OPTIONAL_HDR32_MAGIC
                        { 
                            $IMAGE_NT_HEADER.Add("OptionalHeader",$IMAGE_OPTIONAL_HEADER) 
                        }
                        $IMAGE_NT_OPTIONAL_HDR64_MAGIC
                        {
                            $IMAGE_NT_HEADER.Add("OptionalHeader",$IMAGE_OPTIONAL_HEADER64)
                        } 
                        $IMAGE_ROM_OPTIONAL_HDR_MAGIC { "ROM File, not supported!" }
                        default { "Unknown PE file type $($magic)!" } 
                    }
                    Read-BinaryFile -reader ([ref]$BinaryReader) -Items ([ref]$IMAGE_NT_HEADER)
 
                }
                else
                {
                    Write-Host "Not a valid PE file!"
                }
 
                $dllCharacteristics = Convert-EnumToString -enum ([IMAGE_DLLCHARACTERISTICS]) -value $IMAGE_NT_HEADER["OptionalHeader"]["DllCharacteristics"]
                $subSystem = [Enum]::Parse([IMAGE_SUBSYSTEM],$IMAGE_NT_HEADER["OptionalHeader"]["Subsystem"])
                $machine = [Enum]::Parse([IMAGE_FILE_MACHINE],$IMAGE_NT_HEADER["FileHeader"]["Machine"])

                [UInt64]$originalAddress = ($procAddress.ToInt64() -$dllHandle) + $IMAGE_NT_HEADER.OptionalHeader.ImageBase
                "$($filename)!0x{0:X}" -f $originalAddress
            }
            else
            {
                $symbolName = [NativeMethods]::GetSymbolName($procAddress)
                [Void][NativeMethods]::LdrUnloadDll($dllHandle)
                "$($filename)!$symbolName"
            }
        }
    }

}
Posted in Uncategorized | Leave a comment

Get Directory Listing in Console Tree View via PowerShell

Replicating the behavior of classic command line tool tree in PowerShell. By default only displays directories, add -ShowFileNames switch to show filenames as well.

Script is here https://github.com/chentiangemalc/PowerShellScripts/blob/master/Get-TreeView.ps1

Usage:

./Get-TreeView -Path C:\scripts
./Get-TreeView -Path C:\scripts -ShowFileNames
param (
    [string]$Path = (Get-Location),
    [switch]$ShowFilenames 
)

$global:dirCount = 0
$global:fileCount = 0

Function Register-AbsolutePath {
  param($absolute)
  if (Test-Path -PathType Container $absolute) {
    $global:dirCount++
  } else {
    $global:fileCount++
  }
}

Function Get-Summary {
  return "$($global:dirCount) directories, $($global:fileCount) files"
}

Function Walk-Directory {
  param($directory, $prefix = "")
  
  if ($ShowFilenames)
  {
    $filepaths = @(Get-ChildItem -Path $directory | Sort-Object Name | Select-Object -ExpandProperty Name)
  }
  else
  {
    $filepaths = @(Get-ChildItem -Path $directory -Directory | Sort-Object Name | Select-Object -ExpandProperty Name)
  }
  for ($i = 0; $i -lt $filepaths.Length; $i++) {
    if ($filepaths[$i][0] -eq ".") {
      continue
    }
    $absolute = Join-Path -Path $directory -ChildPath $filepaths[$i]
    Register-AbsolutePath -absolute $absolute
    if ($i -eq $filepaths.Length - 1) {
      Write-Host "$prefix`└── $($filepaths[$i])"
      if (Test-Path -PathType Container $absolute) {
        Walk-Directory -directory $absolute -prefix "$prefix    "
      }
    } else {
      Write-Host "$prefix`├── $($filepaths[$i])"
      if (Test-Path -PathType Container $absolute) {
       Walk-Directory -directory $absolute -prefix "$prefix`│   "
      }
    }
  }
}

Write-Host $path

Walk-Directory -directory $path
Write-Host ""
Write-Host (Get-Summary)

Posted in Uncategorized | Leave a comment

Visual Basic 6 Runtime Debugging Symbols (PDBs)

An issue you will likely come across if debugging VB6 apps with the inbuilt VB6 runtime built into Windows 8+ is that symbols don’t seem to be available via the Microsoft Symbol Server. This makes VB6 stack traces completely bonkers and a lot of work to interpret.

In Visual Basic 5 and earlier debug symbols were provided as a separate download, long since gone from the Microsoft website, although still can be found archived across the internet in files such as VB5SP3DS.EXE. These however do not contain PDB files but only the legacy DBG format symbols.

For Visual Basic 6 the debug symbols were only provided in the ISO service pack releases, the Service Pack 6 is still available via Visual Studio subscriber downloads as mu_visual_basic_6.0_service_pack_6_x86_a783d802.iso (as of Jan 2023)

Within this ISO the DBG files are available via the language folder at root of the ISO i.e. en\us\Vs6sp6B.exe. Once extracted this contains a file msvbvm60.dbg and the matching msvbvm60.dll in Msvbvm60.cab. IDA Pro reports the DBG file mismatch with the DLL however it seems to match functions and other information correctly as far as I can tell.

However on my machine, if using this DLL from the SP6 ISO, while the debugging info is fine in IDA Pro, in WinDbg it triggers downloading a PDB file which doesn’t seem to match correctly, as the start of many functions don’t match up with the symbols in many cases.

A small selection of VB6 runtime DLLs/PDBs were published to the symbol server, but most versions of the DLL I’ve come across do not have the symbols available on the symbol server.

For best success with WinDbg I found using this DLL and PDB file from Microsoft Symbol server works.

https://msdl.microsoft.com/download/symbols/msvbvm60.dll/4802A186153000/msvbvm60.dll

https://msdl.microsoft.com/download/symbols/MSVBVM60.pdb/47193E361/MSVBVM60.pdb

I wouldn’t recommend replacing the VB6 runtime included with Windows, but you can put this in the same folder as your VB6 EXE when debugging if you need to try and get more useful stack traces. It is missing a lot of patches vs the VB6 runtime included with Windows, so this is only useful if you can reproduce the scenario with this runtime. In addition, this version of the runtime is likely missing the latest security patches.

Here is an example with correct symbols, what you will see when a control is clicked:

0:000> k
 # ChildEBP RetAddr      
00 0019f410 734d4738     MSVBVM60!_DoClick
01 0019f428 7347ce03     MSVBVM60!PushCtlProc+0x7c
02 0019f450 7347f800     MSVBVM60!CommonGizWndProc+0xae
03 0019f4ac 7347e1e6     MSVBVM60!StdCtlWndProc+0x232
04 0019f4d0 7347dc27     MSVBVM60!_DefWmCommand+0xc7
05 0019f53c 734a20e3     MSVBVM60!VBDefControlProc+0xb47
06 0019f6bc 7347ce03     MSVBVM60!FormCtlProc+0x10bd
07 0019f6e4 7347f800     MSVBVM60!CommonGizWndProc+0xae
08 0019f740 76d17d52     MSVBVM60!StdCtlWndProc+0x232
09 0019f76c 76cf711a     USER32!_InternalCallWinProc+0x2a
0a 0019f85c 76cf6822     USER32!UserCallWinProcCheckWow+0x4aa
0b 0019f8c0 76d1e29c     USER32!SendMessageWorker+0x842
0c 0019f8e4 76cf5c66     USER32!SendMessageInternal+0x2d
0d 0019f904 76d387bf     USER32!SendMessageW+0x46
0e 0019f928 76d384a7     USER32!xxxButtonNotifyParent+0x50
0f 0019f950 76d37969     USER32!xxxBNReleaseCapture+0x140
10 0019f9f4 76d36de2     USER32!ButtonWndProcWorker+0xad9
11 0019fa20 76d17d52     USER32!ButtonWndProcA+0x52
12 0019fa4c 76cf711a     USER32!_InternalCallWinProc+0x2a
13 0019fb3c 76d0d162     USER32!UserCallWinProcCheckWow+0x4aa
14 0019fb74 7347d03c     USER32!CallWindowProcA+0x82
15 0019fbe0 734d4776     MSVBVM60!VBDefControlProc+0x255
16 0019fc08 7347ce03     MSVBVM60!PushCtlProc+0xbf
17 0019fc30 7347f800     MSVBVM60!CommonGizWndProc+0xae
18 0019fc8c 76d17d52     MSVBVM60!StdCtlWndProc+0x232
19 0019fcb8 76cf711a     USER32!_InternalCallWinProc+0x2a
1a 0019fda8 76cf5a48     USER32!UserCallWinProcCheckWow+0x4aa
1b 0019fe24 76d11af0     USER32!DispatchMessageWorker+0x4b8
1c 0019fe2c 7342a6b0     USER32!DispatchMessageA+0x10
1d 0019fe6c 7342a627     MSVBVM60!ThunderMsgLoop+0xfd
1e 0019fe70 ffffffff     MSVBVM60!CMsoCMHandler::FPushMessageLoop+0x19

Without symbols the same stack trace shows as the following meaningless information. A good clue that this is useless information is the huge offsets after the function name:

0:000> k
 # ChildEBP RetAddr      
WARNING: Stack unwind information not available. Following frames may be wrong.
00 0019f428 7347ce03     MSVBVM60!EbLibraryUnload+0xdcb6
01 0019f450 7347f800     MSVBVM60!IID_IVbaHost+0x2e823
02 0019f4ac 7347e1e6     MSVBVM60!IID_IVbaHost+0x31220
03 0019f4d0 7347dc27     MSVBVM60!IID_IVbaHost+0x2fc06
04 0019f53c 734a20e3     MSVBVM60!IID_IVbaHost+0x2f647
05 0019f6bc 7347ce03     MSVBVM60!BASIC_DISPINTERFACE_GetTICount+0x1cd8b
06 0019f6e4 7347f800     MSVBVM60!IID_IVbaHost+0x2e823
07 0019f740 76d17d52     MSVBVM60!IID_IVbaHost+0x31220
08 0019f76c 76cf711a     USER32!AddClipboardFormatListener+0x52
09 0019f85c 76cf6822     USER32!CallWindowProcW+0x144a
0a 0019f8c0 76d1e29c     USER32!CallWindowProcW+0xb52
0b 0019f8e4 76cf5c66     USER32!SetWindowsHookExAW+0x10c
0c 0019f904 76d387bf     USER32!SendMessageW+0x46
0d 0019f928 76d384a7     USER32!LoadCursorFromFileW+0x1eaf
0e 0019f950 76d37969     USER32!LoadCursorFromFileW+0x1b97
0f 0019f9f4 76d36de2     USER32!LoadCursorFromFileW+0x1059
10 0019fa20 76d17d52     USER32!LoadCursorFromFileW+0x4d2
11 0019fa4c 76cf711a     USER32!AddClipboardFormatListener+0x52
12 0019fb3c 76d0d162     USER32!CallWindowProcW+0x144a
13 0019fb74 7347d03c     USER32!CallWindowProcA+0x82
14 0019fbe0 734d4776     MSVBVM60!IID_IVbaHost+0x2ea5c
15 0019fc08 7347ce03     MSVBVM60!EbLibraryUnload+0xdfaf
16 0019fc30 7347f800     MSVBVM60!IID_IVbaHost+0x2e823
17 0019fc8c 76d17d52     MSVBVM60!IID_IVbaHost+0x31220
18 0019fcb8 76cf711a     USER32!AddClipboardFormatListener+0x52
19 0019fda8 76cf5a48     USER32!CallWindowProcW+0x144a
1a 0019fe24 76d11af0     USER32!DispatchMessageW+0x4d8
1b 0019fe2c 7342a6b0     USER32!DispatchMessageA+0x10
1c 0019fe6c 7342a627     MSVBVM60!_vbaStrToAnsi+0x2f1
1d 0019feb0 00000000     MSVBVM60!_vbaStrToAnsi+0x268
Posted in Uncategorized | Leave a comment

DART Setup Wizard Doesn’t Detect Installed ADK

Trying to create a DART recovery image, got the message during the installation from Microsoft Desktop Optimization Pack 2015 running installer from \DaRT\DaRT 10\Installers\en-us\x64\MSDart100.msi

However, the latest Windows ADK + Windows PE ADK component has been installed. Suspected the issue was a specific version is required, but the download link in the setup is a dead link and just takes you to a generic Microsoft page.

We could look for components not found either through Windows Installer logging, or ProcMon, but here want to demonstrate some ways to analyze how the installer is making the checks.

We can check with ORCA how the ADK installation check is occurring.

Opening the installation MSI in Orca we can set a condition that will prevent the DaRTRecoveryImage feature from installing.;

We could just remove the condition, however was curious how the check actually ocurred…

In Custom Action we can see DetectAdk action

In Binary view we can extract this item by clicking the [Binary Data] and Write Binary to Filename to save the item to disk. Typically these will be a DLL or a Script.

As this is a 32-bit DLL we can test calling this custom action with 32-bit PowerShell

$code = @'
using System;
using System.Runtime.InteropServices;
using System.ComponentModel;
namespace NativeMethods
{
    public static class CustomActionRoutines
    {
        [DllImport("msi.dll", ExactSpelling=true)]
        public static extern IntPtr MsiCreateRecord(uint cParams);
        
        [DllImport("SetupCommonDLLCmp2.dll", SetLastError = true)]
        public static extern uint DetectAdk(IntPtr hMsiHandle);

        [DllImport("msi.dll", ExactSpelling=true)]
        public static extern uint MsiCloseHandle(IntPtr hAny);
     }
}
'@
Add-Type -TypeDefinition $code

$msiHandle = [NativeMethods.CustomActionRoutines]::MsiCreateRecord(0)
[NativeMethods.CustomActionRoutines]::DetectAdk($msiHandle)
[void][NativeMethods.CustomActionRoutines]::MsiCloseHandle($msiHandle)

When we run this while monitoring with Process Monitor we can see it triggers creating a process with the following command line:

rundll32.exe “C:\WINDOWS\SYSTEM32\SetupCommonDLLCmp2.dll”,zzzzInvokeManagedCustomActionOutOfProc SfxCA_5457953 7 Microsoft.Dart.MuCustomActions!Microsoft.Dart.CustomActions.ADKCustomActions.DetectAdk

We can see this extracts a number of files, which are deleted straight after being created. Using 7-zip was able to extract the files from the DLL so we could analyze them:

These DLLs are .NET assemblies, in Microsoft.Dart.MuCustomActions.dll we find with a .NET decompiler a class Microsoft.Dart.CustomActions.ADK.CustomActions with the following code:

// Decompiled with JetBrains decompiler
// Type: Microsoft.Dart.CustomActions.ADKCustomActions
// Assembly: Microsoft.Dart.MuCustomActions, Version=10.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35
// MVID: BB93085A-8824-4EAB-996B-D904BBE67B8D
// Assembly location: D:\reverse\SetupCommonDLLCmp2\Microsoft.Dart.MuCustomActions.dll

using Microsoft.Dart.Commands.Api;
using Microsoft.Deployment.WindowsInstaller;

namespace Microsoft.Dart.CustomActions
{
  public static class ADKCustomActions
  {
    [CustomAction]
    public static ActionResult DetectAdk(Session session)
    {
      if (!WindowsAdk.IsValidAdkPath())
        return ActionResult.Failure;
      session["WINDOWSKITSINSTALLED"] = "1";
      return ActionResult.Success;
    }
  }
}

This references the following ADK related queries:

// Decompiled with JetBrains decompiler
// Type: Microsoft.Dart.Commands.Api.WindowsAdk
// Assembly: Microsoft.Dart.MuCustomActions, Version=10.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35
// MVID: BB93085A-8824-4EAB-996B-D904BBE67B8D
// Assembly location: D:\reverse\SetupCommonDLLCmp2\Microsoft.Dart.MuCustomActions.dll

using Microsoft.Win32;
using System;
using System.IO;

namespace Microsoft.Dart.Commands.Api
{
  internal static class WindowsAdk
  {
    private static string adkPath;
    private static string bootFilesPathx64;
    private static string bootFilesPathx86;
    private static string oscdImagePathx64;
    private static string oscdImagePathx86;
    private static string imagePathx64;
    private static string imagePathx86;
    private static string optionalComponentPathx64;
    private static string optionalComponentPathx86;

    public static string AdkPath
    {
      get
      {
        if (WindowsAdk.adkPath == null)
        {
          string str = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), "Windows Kits\\10\\");
          RegistryKey registryKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32).OpenSubKey("SOFTWARE\\Microsoft\\Windows Kits\\Installed Roots");
          if (registryKey != null)
            WindowsAdk.adkPath = registryKey.GetValue("KitsRoot10", (object) null) as string;
          if (WindowsAdk.adkPath == null)
            WindowsAdk.adkPath = str;
        }
        return WindowsAdk.adkPath;
      }
      internal set => WindowsAdk.adkPath = value;
    }

    public static bool IsValidAdkPath() => Directory.Exists(WindowsAdk.BootFilesPathx64) && Directory.Exists(WindowsAdk.BootFilesPathx86) && Directory.Exists(WindowsAdk.OscdImagePathx64) && Directory.Exists(WindowsAdk.OscdImagePathx86) && Directory.Exists(WindowsAdk.ImagePathx64) && Directory.Exists(WindowsAdk.ImagePathx86) && Directory.Exists(WindowsAdk.OptionalComponentPathx64) && Directory.Exists(WindowsAdk.OptionalComponentPathx86);

    public static string BootFilesPathx64
    {
      get
      {
        if (WindowsAdk.bootFilesPathx64 == null)
          WindowsAdk.bootFilesPathx64 = Path.Combine(WindowsAdk.AdkPath, "Assessment and Deployment Kit\\Windows Preinstallation Environment\\amd64\\Media");
        return WindowsAdk.bootFilesPathx64;
      }
      internal set => WindowsAdk.bootFilesPathx64 = value;
    }

    public static string BootFilesPathx86
    {
      get
      {
        if (WindowsAdk.bootFilesPathx86 == null)
          WindowsAdk.bootFilesPathx86 = Path.Combine(WindowsAdk.AdkPath, "Assessment and Deployment Kit\\Windows Preinstallation Environment\\x86\\Media");
        return WindowsAdk.bootFilesPathx86;
      }
      internal set => WindowsAdk.bootFilesPathx86 = value;
    }

    public static string OscdImagePathx64
    {
      get
      {
        if (WindowsAdk.oscdImagePathx64 == null)
          WindowsAdk.oscdImagePathx64 = Path.Combine(WindowsAdk.AdkPath, "Assessment and Deployment Kit\\Deployment Tools\\amd64\\Oscdimg");
        return WindowsAdk.oscdImagePathx64;
      }
      internal set => WindowsAdk.oscdImagePathx64 = value;
    }

    public static string OscdImagePathx86
    {
      get
      {
        if (WindowsAdk.oscdImagePathx86 == null)
          WindowsAdk.oscdImagePathx86 = Path.Combine(WindowsAdk.AdkPath, "Assessment and Deployment Kit\\Deployment Tools\\x86\\Oscdimg");
        return WindowsAdk.oscdImagePathx86;
      }
      internal set => WindowsAdk.oscdImagePathx86 = value;
    }

    public static string ImagePathx64
    {
      get
      {
        if (WindowsAdk.imagePathx64 == null)
          WindowsAdk.imagePathx64 = Path.Combine(WindowsAdk.AdkPath, "Assessment and Deployment Kit\\Windows Preinstallation Environment\\amd64\\en-us");
        return WindowsAdk.imagePathx64;
      }
      internal set => WindowsAdk.imagePathx64 = value;
    }

    public static string ImagePathx86
    {
      get
      {
        if (WindowsAdk.imagePathx86 == null)
          WindowsAdk.imagePathx86 = Path.Combine(WindowsAdk.AdkPath, "Assessment and Deployment Kit\\Windows Preinstallation Environment\\x86\\en-us");
        return WindowsAdk.imagePathx86;
      }
      internal set => WindowsAdk.imagePathx86 = value;
    }

    public static string OptionalComponentPathx64
    {
      get
      {
        if (WindowsAdk.optionalComponentPathx64 == null)
          WindowsAdk.optionalComponentPathx64 = Path.Combine(WindowsAdk.AdkPath, "Assessment and Deployment Kit\\Windows Preinstallation Environment\\amd64\\WinPE_OCs");
        return WindowsAdk.optionalComponentPathx64;
      }
      internal set => WindowsAdk.optionalComponentPathx64 = value;
    }

    public static string OptionalComponentPathx86
    {
      get
      {
        if (WindowsAdk.optionalComponentPathx86 == null)
          WindowsAdk.optionalComponentPathx86 = Path.Combine(WindowsAdk.AdkPath, "Assessment and Deployment Kit\\Windows Preinstallation Environment\\x86\\WinPE_OCs");
        return WindowsAdk.optionalComponentPathx86;
      }
      internal set => WindowsAdk.optionalComponentPathx86 = value;
    }

    public static string GetWinPeOcsPath(Architecture architecture)
    {
      switch (architecture)
      {
        case Architecture.X86:
          return WindowsAdk.OptionalComponentPathx86;
        case Architecture.X64:
          return WindowsAdk.OptionalComponentPathx64;
        default:
          return string.Empty;
      }
    }
  }
}

From reviewing this we can see that all of the following paths must exist for ADK to be detected as “installed”

WindowsAdk.BootFilesPathx64
WindowsAdk.BootFilesPathx86
WindowsAdk.OscdImagePathx64
WindowsAdk.OscdImagePathx86
WindowsAdk.ImagePathx64
WindowsAdk.ImagePathx86
WindowsAdk.OptionalComponentPathx64
WindowsAdk.OptionalComponentPathx86

We could test the logic with this PowerShell code:

Add-Type -Path "Microsoft.Dart.MuCustomActions.dll"
Add-Type -Path "Microsoft.Deployment.WindowsInstaller.dll"
[Microsoft.Dart.CustomActions.ADKCustomActions]::DetectAdk($null)

This reports “Failure” as expected. However it doesn’t clearly show which of the paths missing is triggering the issue. We can break the logic of the check into some pure PowerShell and show as the result per path:

class WindowsAdk
{
    [string]$adkPath
    [string]$bootFilesPathx64
    [string]$bootFilesPathx86
    [string]$oscdImagePathx64
    [string]$oscdImagePathx86
    [string]$imagePathx64
    [string]$imagePathx86
    [string]$optionalComponentPathx64
    [string]$optionalComponentPathx86
}

$WindowsADK = New-Object WindowsADK
$WindowsADK.adkPath = [System.IO.Path]::Combine([Environment]::GetFolderPath("ProgramFilesX86"), "Windows Kits\10\")
$key = [Microsoft.Win32.RegistryKey]::OpenBaseKey([Microsoft.Win32.RegistryHive]::LocalMachine, [Microsoft.Win32.RegistryView]::Registry32).OpenSubKey("SOFTWARE\Microsoft\\Windows Kits\Installed Roots")
if ($key -ne $null)
{
    $WindowsADK.adkPath = $key.GetValue("KitsRoot10")
}


$windowsADK.bootFilesPathx64 = [System.IO.Path]::Combine($WindowsADK.adkPath, "Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\Media")
$windowsADK.bootFilesPathx86 = [System.IO.Path]::Combine($WindowsADK.adkPath, "Assessment and Deployment Kit\Windows Preinstallation Environment\x86\Media")
$windowsADK.oscdImagePathx64 = [System.IO.Path]::Combine($WindowsADK.adkPath,"Assessment and Deployment Kit\Deployment Tools\amd64\Oscdimg")
$windowsADK.oscdImagePathx86 = [System.IO.Path]::Combine($WindowsADK.adkPath, "Assessment and Deployment Kit\Deployment Tools\x86\Oscdimg")
$windowsADK.imagePathx64 = [System.IO.Path]::Combine($WindowsADK.adkPath, "Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\en-us")
$windowsADK.imagePathx86 = [System.IO.Path]::Combine($WindowsADK.adkPath, "Assessment and Deployment Kit\Windows Preinstallation Environment\x86\en-us")
$windowsADK.optionalComponentPathx64 = [System.IO.Path]::Combine($WindowsADK.adkPath,"Assessment and Deployment Kit\Windows Preinstallation Environment\amd64\WinPE_OCs")
$windowsADK.optionalComponentPathx86 = [System.IO.Path]::Combine($WindowsADK.adkPath,"Assessment and Deployment Kit\Windows Preinstallation Environment\x86\WinPE_OCs")

"Windows ADK Path : $(Test-Path $WindowsADK.adkPath)"
"x64 Boot Files Path : $(Test-Path $WindowsADK.bootFilesPathx64)"
"x86 Boot Files Path : $(Test-Path $WindowsADK.bootFilesPathx86)"
"x64 OSCD Files Path : $(Test-Path $WindowsADK.oscdImagePathx64)"
"x86 OSCD Files Path : $(Test-Path $WindowsADK.oscdImagePathx86)"
"x64 Image Path : $(Test-Path $WindowsADK.imagePathx64)"
"x86 Image Path : $(Test-Path $WindowsADK.imagePathx86)"
"x64 Optional Components Path : $(Test-Path $WindowsADK.optionalComponentPathx64)"
"x86 Optional Components Path : $(Test-Path $WindowsADK.optionalComponentPathx86)"

On my machine this output the following:

So we ran these two commands to fix the missing folders. I didn’t want to create any 32-bit images so expected these missing shouldn’t be a problem:

mkdir "C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\x86\Media"
mkdir "C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\x86\WinPE_OCs"
mkdir "C:\Program Files (x86)\Windows Kits\10\Assessment and Deployment Kit\Windows Preinstallation Environment\x86\en-us"

Now the installer works, and recovery images are generated fine…

Posted in Uncategorized | 3 Comments

Merge Multiple PDFs with PowerShell and PDFSharp

First you will need to download PDF Sharp and build with Visual Studio the solution “BuildAll-PdfSharp.sln” and then obtaining the output PdfSharp.dll and placing in same directory as script.

Takes a specified folder of PDFs and combines them into an output file. Modify sourcePath and outputPath as necessary.

$sourcePath = "<path to folder with PDFs to merge>" 
$outputPath = "<filename of final PDF>"

Add-Type -Path "$($PSScriptRoot)\PdfSharp.dll"
$pdfDestination = New-Object PdfSharp.Pdf.PdfDocument
$pdfFiles = Get-ChildItem -Path $sourcePath -Filter *.pdf
ForEach ($filename in $pdfFiles)
{
    "Adding '$($filename.Fullname)'"
    $pdfSource = [PdfSharp.Pdf.IO.PdfReader]::Open($filename.Fullname,[PdfSharp.Pdf.IO.PdfDocumentOpenMode]::Import)
    for ($i = 0; $i -lt $pdfSource.PageCount; $i++)
    {
        $pdfDestination.AddPage($pdfSource.Pages[$i])
    }

    $pdfSource.Close()
     
}

$pdfDestination.Save($outputPath)
Posted in Uncategorized | Leave a comment

Extract Access Database Password with PowerShell

Previously we looked at removing Office Macro Passwords with PowerShell here.

This script can be used to retrieve the master password (i.e. database design password) for many Microsoft Access Database files (.mdb) Note this does not work with databases that have multiple user/passwords associated with them. Only tested with Access 2003-2007 format. May work with older formats, or there may be some minimal tweaking required.

The script is available here https://github.com/chentiangemalc/PowerShellScripts/blob/master/Get-MdbPassword.ps1

Example usage:

<#
.SYNOPSIS

    Displays the password of an Access 2003-2007 (MDB) file

.DESCRIPTION

    Decrypts Access Database password.

.PARAMETER Path

    The access database file of which to display the password.
        
.INPUTS

  None
  
.OUTPUTS

  None
  
.NOTES

  Version:        1.0

  Author:         chentiangemalc

  Creation Date:  6 Sep 2022

  Purpose/Change: Initial script development

  
.EXAMPLE

  .\Get-MdbPassword.ps1 -Path c:\test\test.mdb

#>

[CmdletBinding()]Param(
[Parameter(Mandatory=$true)]
[ValidateScript({
    if( -Not ($_ | Test-Path) ){
        throw "File or folder does not exist"
    }

    if(-Not ($_ | Test-Path -PathType Leaf) ){
        throw "The Path argument must be a file. Folder paths are not allowed."
    }
    return $true
})]
[string]$Path)

[Byte[]]$global:decoderKey = @( 
   0xBA,0x6A,0xEC,0x37,0x61,0xD5,0x9C,0xFA,0xFA,
   0xCF,0x28,0xE6,0x2F,0x27,0x8A,0x60,0x68,0x05,
   0x7B,0x36,0xC9,0xE3,0xDF,0xB1,0x4B,0x65,0x13,
   0x43,0xF3,0x3E,0xB1,0x33,0x08,0xF0,0x79,0x5B,
   0xAE,0x24,0x7C,0x2A,0x00,0x00,0x00,0x00)

Function Decode-Data([Byte[]]$data,[System.Text.Encoding]$Encoding)
{
    switch($Encoding.EncodingName)
    {
        "Unicode" { $decodeSize = 40 }
        default: { throw "Unknown encoding type" }
    }

    $dataPosition = 0
    [Byte]$key1 = $global:decoderKey[36] -bxor $data[36]
    [Byte]$key3 = $global:decoderKey[37] -bxor $data[37]
    [byte]$key4 = 0
    
    for ($counter = 0; $counter -lt $decodeSize;$counter++)
    {
        $key4 = $data[$counter] -bxor $global:decoderKey[$counter]
        $data[$counter]=$key4
        if (!($counter % 4)) { $data[$counter] = $key1 -bxor $key4 }
        if (($counter % 4) -eq 1) { $data[$counter] = $data[$counter] -bxor $key3 }

    }

    $outString = $encoding.GetString($data)
    $outString = $outString.Substring(0,$outString.IndexOf([Char]0))
    $outString 
}

$stream = [System.IO.File]::Open($path, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read, [System.IO.FileShare]::ReadWrite)
$reader = New-Object System.IO.BinaryReader($stream)
$chunk = $reader.ReadBytes(128)
if ($chunk[4] -eq 0x53 -and
    $chunk[5] -eq 0x74 -and
    $chunk[6] -eq 0x61 -and
    $chunk[13] -eq 0x4A -and
    $chunk[14] -eq 0x65)
{
    [void]$reader.BaseStream.Seek(66, [System.IO.SeekOrigin]::Begin)
    $chunk = $reader.ReadBytes(128)
    if ($chunk[90] -eq 0x34 -and
        $chunk[91] -eq 0x2E -and
        $chunk[92] -eq 0x30)
    {
        Decode-Data -Data $chunk -Encoding ([System.Text.Encoding]::Unicode)
        
    }
    else
    {
        # note: Expect ASCII encoding based file to use decode size of 85 
        # but don't have any example file to test with at the moment
        throw "Unknown Encoding"
    }
}
else
{
    throw "Unexpected data!"
}

$reader.Close()
$stream.Close()
Posted in Uncategorized | Leave a comment

Display Binary Numbers and Struct Data with Printf in WinDbg

Was comparing an application behavior between Windows XP and Windows 10 and needed to check the value of some structs, without symbol information for them. The values I wanted to check were specific bits in the struct passed as the 2nd parameter to a function. In this case, I wanted to display the contents of DCB struct, but in an easy-to-read format when comparing traces. In addition, was working in an environment where it was difficult to copy files in/out so using/writing an extension wasn’t a suitable option.

This is fairly straightforward with WinDbg Preview and the JavaScript capability, and normally have used the SyntheticTypes script to parse simple C headers with the relevant struct https://github.com/microsoft/WinDbg-Samples/tree/master/SyntheticTypes

We can use .formats command but this offers limited if any formatting capability.

0:000> .formats $t0
Evaluate expression:
  Hex:     7642b9ad
  Decimal: 1984084397
  Octal:   16620534655
  Binary:  01110110 01000010 10111001 10101101
  Chars:   vB..
  Time:    Mon Nov 15 09:33:17 2032
  Float:   low 9.87375e+032 high 0
  Double:  9.80268e-315

If you can use the JavaScript capability I would use it instead of this approach.

There may be better ways to achieve this with legacy WinDbg and native scripting, but this approach while tedious does work.

One approach can use a combination of boolean and, left shift and right shifts to extract specific bits. For a 32-bit value position “31” would be the first binary digit, and position “0” would have the final digit.

This can be done concisely with a loop, however, in WinDbg this is extremely slow, taking about a minute on my machine to run the loop. Here we will assume the value we want to display in binary is stored in $t0.

0:000> .for (r $t1 = 0x1F; $t1 >= 0 ; r $t1 = $t1-1) { .printf "%d",($t0 & ( 1 << $t1 )) >> $t1 }
01110110010000101011100110101101

A speedier version is:

0:000>  .printf "%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d",($t0 & ( 1 << 0x1F)) >> 0x1F,($t0 & ( 1 << 0x1E)) >> 0x1E,($t0 & ( 1 << 0x1D)) >> 0x1D,($t0 & ( 1 << 0x1C)) >> 0x1C,($t0 & ( 1 << 0x1B)) >> 0x1B,($t0 & ( 1 << 0x1A)) >> 0x1A,($t0 & ( 1 << 0x19)) >> 0x19,($t0 & ( 1 << 0x18)) >> 0x18,($t0 & ( 1 << 0x17)) >> 0x17,($t0 & ( 1 << 0x16)) >> 0x16,($t0 & ( 1 << 0x15)) >> 0x15,($t0 & ( 1 << 0x14)) >> 0x14,($t0 & ( 1 << 0x13)) >> 0x13,($t0 & ( 1 << 0x12)) >> 0x12,($t0 & ( 1 << 0x11)) >> 0x11,($t0 & ( 1 << 0x10)) >> 0x10,($t0 & ( 1 << 0xF)) >> 0xF,($t0 & ( 1 << 0xE)) >> 0xE,($t0 & ( 1 << 0xD)) >> 0xD,($t0 & ( 1 << 0xC)) >> 0xC,($t0 & ( 1 << 0xB)) >> 0xB,($t0 & ( 1 << 0xA)) >> 0xA,($t0 & ( 1 << 0x9)) >> 0x9,($t0 & ( 1 << 0x8)) >> 0x8,($t0 & ( 1 << 0x7)) >> 0x7,($t0 & ( 1 << 0x6)) >> 0x6,($t0 & ( 1 << 0x5)) >> 0x5,($t0 & ( 1 << 0x4)) >> 0x4,($t0 & ( 1 << 0x3)) >> 0x3,($t0 & ( 1 << 0x2)) >> 0x2,($t0 & ( 1 << 0x1)) >> 0x1,($t0 & ( 1 << 0x0)) >> 0x0
01110110010000101011100110101101

If we look at our struct debugging on a target that has debugging symbols, we can see some values are not just individual “bits” but take up multiple positions i.e. bits 15-31 for fDummy2

0:000> dx -r1 ((DcbTest!_DCB *)0x4ffb9c)
((DcbTest!_DCB *)0x4ffb9c)                 : 0x4ffb9c [Type: _DCB *]
    [+0x000] DCBlength        : 0x1c [Type: unsigned long]
    [+0x004] BaudRate         : 0x2580 [Type: unsigned long]
    [+0x008 ( 0: 0)] fBinary          : 0x1 [Type: unsigned long]
    [+0x008 ( 1: 1)] fParity          : 0x0 [Type: unsigned long]
    [+0x008 ( 2: 2)] fOutxCtsFlow     : 0x1 [Type: unsigned long]
    [+0x008 ( 3: 3)] fOutxDsrFlow     : 0x0 [Type: unsigned long]
    [+0x008 ( 5: 4)] fDtrControl      : 0x2 [Type: unsigned long]
    [+0x008 ( 6: 6)] fDsrSensitivity  : 0x1 [Type: unsigned long]
    [+0x008 ( 7: 7)] fTXContinueOnXoff : 0x0 [Type: unsigned long]
    [+0x008 ( 8: 8)] fOutX            : 0x1 [Type: unsigned long]
    [+0x008 ( 9: 9)] fInX             : 0x0 [Type: unsigned long]
    [+0x008 (10:10)] fErrorChar       : 0x1 [Type: unsigned long]
    [+0x008 (11:11)] fNull            : 0x0 [Type: unsigned long]
    [+0x008 (13:12)] fRtsControl      : 0x3 [Type: unsigned long]
    [+0x008 (14:14)] fAbortOnError    : 0x1 [Type: unsigned long]
    [+0x008 (31:15)] fDummy2          : 0x19999 [Type: unsigned long]
    [+0x00c] wReserved        : 0xcccc [Type: unsigned short]
    [+0x00e] XonLim           : 0x7b [Type: unsigned short]
    [+0x010] XoffLim          : 0x1c8 [Type: unsigned short]
    [+0x012] ByteSize         : 0x8 [Type: unsigned char]
    [+0x013] Parity           : 0x4 [Type: unsigned char]
    [+0x014] StopBits         : 0x2 [Type: unsigned char]
    [+0x015] XonChar          : 23 [Type: char]
    [+0x016] XoffChar         : 45 '-' [Type: char]
    [+0x017] ErrorChar        : 67 'C' [Type: char]
    [+0x018] EofChar          : 89 'Y' [Type: char]
    [+0x019] EvtChar          : 98 'b' [Type: char]
    [+0x01a] wReserved1       : 0xcccc [Type: unsigned short]

We can get the binary value by taking those bit positions. In this case, our value is the 2nd parameter in the 32-bit standard calling convention. As we have just hit a breakpoint where the function starts we find the struct base address at poi(@esp+8) and add 8 for the offset where our 32-bit value we are breaking apart begins.

0:000> r $t0= poi(poi(@esp+8)+8)
0:000> .printf "%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d%d",($t0 & ( 1 << 0x1F)) >> 0x1F,($t0 & ( 1 << 0x1E)) >> 0x1E,($t0 & ( 1 << 0x1D)) >> 0x1D,($t0 & ( 1 << 0x1C)) >> 0x1C,($t0 & ( 1 << 0x1B)) >> 0x1B,($t0 & ( 1 << 0x1A)) >> 0x1A,($t0 & ( 1 << 0x19)) >> 0x19,($t0 & ( 1 << 0x18)) >> 0x18,($t0 & ( 1 << 0x17)) >> 0x17,($t0 & ( 1 << 0x16)) >> 0x16,($t0 & ( 1 << 0x15)) >> 0x15,($t0 & ( 1 << 0x14)) >> 0x14,($t0 & ( 1 << 0x13)) >> 0x13,($t0 & ( 1 << 0x12)) >> 0x12,($t0 & ( 1 << 0x11)) >> 0x11,($t0 & ( 1 << 0x10)) >> 0x10,($t0 & ( 1 << 0xF)) >> 0xF
11001100110011001   

To display it as a number we can add the bits together and use left shifts to convert it back to a value we can display as hex or decimal.

0:000> .printf "0x%X",((($t0 & ( 1 << 0x1F)) >> 1F) << 0x10) + ((($t0 & ( 1 << 0x1E)) >> 1E) << 0xF) + ((($t0 & ( 1 << 0x1D)) >> 1D) << 0xE) + ((($t0 & ( 1 << 0x1C)) >> 1C) << 0xD) + ((($t0 & ( 1 << 0x1B)) >> 1B) << 0xC) + ((($t0 & ( 1 << 0x1A)) >> 1A) << 0xB) + ((($t0 & ( 1 << 0x19)) >> 19) << 0xA) + ((($t0 & ( 1 << 0x18)) >> 18) << 0x9) + ((($t0 & ( 1 << 0x17)) >> 17) << 0x8) + ((($t0 & ( 1 << 0x16)) >> 16) << 0x7) + ((($t0 & ( 1 << 0x15)) >> 15) << 0x6) + ((($t0 & ( 1 << 0x14)) >> 14) << 0x5) + ((($t0 & ( 1 << 0x13)) >> 13) << 0x4) + ((($t0 & ( 1 << 0x12)) >> 12) << 0x3) + ((($t0 & ( 1 << 0x11)) >> 11) << 0x2) + ((($t0 & ( 1 << 0x10)) >> 10) << 0x1) + ((($t0 & ( 1 << 0xF)) >> F) << 0x0)
0x19999

This is fairly tedious to type out so used a simple PowerShell script I can use to generate the value to type in and set the text to the clipboard to paste into WinDbg:

$bitStart = 31
$bitEnd = 15

$cmd = ""
For ($i = $bitStart;$i -ge $bitEnd;$i --)
{
    $cmd+=[String]::Format("(((`$t0 & ( 1 << 0x{0:X})) >> {0:X}) << 0x{1:X})",$i,$i-$bitEnd)
    if ($i -ne $bitEnd) { $cmd += " + " }
}

$cmd | Set-Clipboard

Combining these techniques we can print out our struct now in an easy to read format. (Well the output is easy to read, not the command…)

0:000> r $t0 = poi(poi(@esp+8)+8);.printf "DCBlength=%i\nBaudRate=%i\nfBinary=%d\nfParity=%d\nfOutxCtsFlow=%d\nfOutxDsrFlow=%d\nfDtrControl=%d\nfDsrSensitivity=%d\nfTXContinueOnXoff=%d\nfOutX=%d\nfInX=%d\nfErrorChar=%d\nfNull=%d\nfRtsControl=%d\nfAbortOnError=%d\n",poi(poi(@esp+8)),poi(poi(@esp+8)+4),($t0 & ( 1 << 0x0)) >> 0x0,($t0 & ( 1 << 0x1)) >> 0x1,($t0 & ( 1 << 0x2)) >> 0x2,($t0 & ( 1 << 0x3)) >> 0x3,((($t0 & ( 1 << 0x5)) >> 5) << 0x1) + ((($t0 & ( 1 << 0x4)) >> 4) << 0x0),($t0 & ( 1 << 0x6)) >> 0x6,($t0 & ( 1 << 0x7)) >> 0x7,($t0 & ( 1 << 0x8)) >> 0x8,($t0 & ( 1 << 0x9)) >> 0x9,($t0 & ( 1 << 0xA)) >> 0xA,($t0 & ( 1 << 0xB)) >> 0xB,((($t0 & ( 1 << 0xD)) >> D) << 0x1) + ((($t0 & ( 1 << 0xC)) >> C) << 0x0),($t0 & ( 1 << 0xE)) >> 0xE;r $t0=poi(poi(@esp+8)+0xe);r $t0=poi(poi(@esp+8)+0xe);.printf "XonLim=%i\nXoffLim=%i\n",((($t0 & ( 1 << 0xF)) >> F) << 0xF) + ((($t0 & ( 1 << 0xE)) >> E) << 0xE) + ((($t0 & ( 1 << 0xD)) >> D) << 0xD) + ((($t0 & ( 1 << 0xC)) >> C) << 0xC) + ((($t0 & ( 1 << 0xB)) >> B) << 0xB) + ((($t0 & ( 1 << 0xA)) >> A) << 0xA) + ((($t0 & ( 1 << 0x9)) >> 9) << 0x9) + ((($t0 & ( 1 << 0x8)) >> 8) << 0x8) + ((($t0 & ( 1 << 0x7)) >> 7) << 0x7) + ((($t0 & ( 1 << 0x6)) >> 6) << 0x6) + ((($t0 & ( 1 << 0x5)) >> 5) << 0x5) + ((($t0 & ( 1 << 0x4)) >> 4) << 0x4) + ((($t0 & ( 1 << 0x3)) >> 3) << 0x3) + ((($t0 & ( 1 << 0x2)) >> 2) << 0x2) + ((($t0 & ( 1 << 0x1)) >> 1) << 0x1) + ((($t0 & ( 1 << 0x0)) >> 0) << 0x0),((($t0 & ( 1 << 0x1F)) >> 1F) << 0xF) + ((($t0 & ( 1 << 0x1E)) >> 1E) << 0xE) + ((($t0 & ( 1 << 0x1D)) >> 1D) << 0xD) + ((($t0 & ( 1 << 0x1C)) >> 1C) << 0xC) + ((($t0 & ( 1 << 0x1B)) >> 1B) << 0xB) + ((($t0 & ( 1 << 0x1A)) >> 1A) << 0xA) + ((($t0 & ( 1 << 0x19)) >> 19) << 0x9) + ((($t0 & ( 1 << 0x18)) >> 18) << 0x8) + ((($t0 & ( 1 << 0x17)) >> 17) << 0x7) + ((($t0 & ( 1 << 0x16)) >> 16) << 0x6) + ((($t0 & ( 1 << 0x15)) >> 15) << 0x5) + ((($t0 & ( 1 << 0x14)) >> 14) << 0x4) + ((($t0 & ( 1 << 0x13)) >> 13) << 0x3) + ((($t0 & ( 1 << 0x12)) >> 12) << 0x2) + ((($t0 & ( 1 << 0x11)) >> 11) << 0x1) + ((($t0 & ( 1 << 0x10)) >> 10) << 0x0);r $t0=poi(poi(@esp+8)+0x12);.printf "ByteSize=%d\nParity=%d\nStopBits=%d\nXonChar='%C'\nXoffChar='%C'\nErrorChar='%C'\nEofChar='%C'\nEvtChar='%C'\n",((($t0 & ( 1 << 0x7)) >> 7) << 0x7) + ((($t0 & ( 1 << 0x6)) >> 6) << 0x6) + ((($t0 & ( 1 << 0x5)) >> 5) << 0x5) + ((($t0 & ( 1 << 0x4)) >> 4) << 0x4) + ((($t0 & ( 1 << 0x3)) >> 3) << 0x3) + ((($t0 & ( 1 << 0x2)) >> 2) << 0x2) + ((($t0 & ( 1 << 0x1)) >> 1) << 0x1) + ((($t0 & ( 1 << 0x0)) >> 0) << 0x0),((($t0 & ( 1 << 0xF)) >> F) << 0x7) + ((($t0 & ( 1 << 0xE)) >> E) << 0x6) + ((($t0 & ( 1 << 0xD)) >> D) << 0x5) + ((($t0 & ( 1 << 0xC)) >> C) << 0x4) + ((($t0 & ( 1 << 0xB)) >> B) << 0x3) + ((($t0 & ( 1 << 0xA)) >> A) << 0x2) + ((($t0 & ( 1 << 0x9)) >> 9) << 0x1) + ((($t0 & ( 1 << 0x8)) >> 8) << 0x0),((($t0 & ( 1 << 0x17)) >> 17) << 0x7) + ((($t0 & ( 1 << 0x16)) >> 16) << 0x6) + ((($t0 & ( 1 << 0x15)) >> 15) << 0x5) + ((($t0 & ( 1 << 0x14)) >> 14) << 0x4) + ((($t0 & ( 1 << 0x13)) >> 13) << 0x3) + ((($t0 & ( 1 << 0x12)) >> 12) << 0x2) + ((($t0 & ( 1 << 0x11)) >> 11) << 0x1) + ((($t0 & ( 1 << 0x10)) >> 10) << 0x0),poi(poi(@esp+8)+0x15),poi(poi(@esp+8)+0x16),poi(poi(@esp+8)+0x17),poi(poi(@esp+8)+0x18),poi(poi(@esp+8)+0x19)
DCBlength=28
BaudRate=9600
fBinary=1
fParity=0
fOutxCtsFlow=1
fOutxDsrFlow=0
fDtrControl=2
fDsrSensitivity=1
fTXContinueOnXoff=0
fOutX=1
fInX=0
fErrorChar=1
fNull=0
fRtsControl=3
fAbortOnError=1
XonLim=123
XoffLim=456
ByteSize=8
Parity=4
StopBits=2
XonChar=''
XoffChar='-'
ErrorChar='C'
EofChar='Y'
EvtChar='b'

We can also split a 32-bit number stored in $t0 into four separate 1 byte values using this approach, allowing us to print an 8-bit value as a number. And provides an alternative in older versions of WinDbg which don’t support %C for ASCII char output.

0:000> r $t0 = poi(poi(@esp+8)+0x15)
0:000> r $t1 = ((($t0 & ( 1 << 0x7)) >> 7) << 0x7) + ((($t0 & ( 1 << 0x6)) >> 6) << 0x6) + ((($t0 & ( 1 << 0x5)) >> 5) << 0x5) + ((($t0 & ( 1 << 0x4)) >> 4) << 0x4) + ((($t0 & ( 1 << 0x3)) >> 3) << 0x3) + ((($t0 & ( 1 << 0x2)) >> 2) << 0x2) + ((($t0 & ( 1 << 0x1)) >> 1) << 0x1) + ((($t0 & ( 1 << 0x0)) >> 0) << 0x0);r $t2 = ((($t0 & ( 1 << 0xF)) >> F) << 0x7) + ((($t0 & ( 1 << 0xE)) >> E) << 0x6) + ((($t0 & ( 1 << 0xD)) >> D) << 0x5) + ((($t0 & ( 1 << 0xC)) >> C) << 0x4) + ((($t0 & ( 1 << 0xB)) >> B) << 0x3) + ((($t0 & ( 1 << 0xA)) >> A) << 0x2) + ((($t0 & ( 1 << 0x9)) >> 9) << 0x1) + ((($t0 & ( 1 << 0x8)) >> 8) << 0x0);r $t3 = ((($t0 & ( 1 << 0x17)) >> 17) << 0x7) + ((($t0 & ( 1 << 0x16)) >> 16) << 0x6) + ((($t0 & ( 1 << 0x15)) >> 15) << 0x5) + ((($t0 & ( 1 << 0x14)) >> 14) << 0x4) + ((($t0 & ( 1 << 0x13)) >> 13) << 0x3) + ((($t0 & ( 1 << 0x12)) >> 12) << 0x2) + ((($t0 & ( 1 << 0x11)) >> 11) << 0x1) + ((($t0 & ( 1 << 0x10)) >> 10) << 0x0);r $t4 = ((($t0 & ( 1 << 0x1F)) >> 1F) << 0x7) + ((($t0 & ( 1 << 0x1E)) >> 1E) << 0x6) + ((($t0 & ( 1 << 0x1D)) >> 1D) << 0x5) + ((($t0 & ( 1 << 0x1C)) >> 1C) << 0x4) + ((($t0 & ( 1 << 0x1B)) >> 1B) << 0x3) + ((($t0 & ( 1 << 0x1A)) >> 1A) << 0x2) + ((($t0 & ( 1 << 0x19)) >> 19) << 0x1) + ((($t0 & ( 1 << 0x18)) >> 18) << 0x0)
0:000> .printf "%i, %i, %i, %i",$t1,$t2,$t3,$t4
23, 45, 67, 89
Posted in Uncategorized | Leave a comment

Adding A Pause Between Items in Config.NT / Config.Sys

I wanted to debug startup of a 16-bit DOS driver on 32-bit Windows 10 with NTVDM, however attempts to attach debugger / Time Travel Debugging Trace to NTVDM startup process was triggering access violations and causing NTVDM.exe to crash. Once NTVDM had started I could attach debugger fine, but was missing the driver startup code I wanted to capture.

MS-DOS 6.00 added a feature where F8 could be pressed to run autoexec.bat/config.sys entries one line at a time, but I haven’t found an alternative that works with c:\windows\system32\config.nt in Windows.

In this case using Microsoft Macro Assmbler built this driver with the following commands:

masm wait.asm
link wait
exe2bin wait.exe wait.sys
xcopy wait.sys C:\windows\system32

The code is here, this can also be used a template for a simple MS-DOS driver.

; *******************************************************************
; * Press Any Key To Continue DRIVER                                *
; *******************************************************************

cseg        segment para    public  'code'
wait        proc    far
            assume  cs:cseg,es:cseg,ds:cseg

; *******************************************************************
; * MAIN PROCEDURE CODE                                             *
; *******************************************************************

begin:

; *******************************************************************
; * DEVICE HEADER - REQUIRED BY DOS                                 *
; *******************************************************************

next_dev    dd  -1              ; no other device drivers
attribute   dw  8000h           ; character device
strategy    dw  dev_strategy    ; address of 1st dos call
interrupt   dw  dev_interrupt   ; address of 2nd dos call
dev_name    db  'WAIT$ '      ; name of the driver

; *******************************************************************
; * WORK SPACE FOR THE DEVICE DRIVER                                *
; *******************************************************************

rh_ofs      dw  ?               ; request header offset
rh_seg      dw  ?               ; request header segment
msg1        db  'Waiting...'
            db  0dh,0ah,'$'
seconds     db 0   
counter     db 0  
crlf	    db 0dh,0ah,'$'
; *******************************************************************
; * THE STRATEGY PROCEDURE                                          *
; *******************************************************************

dev_strategy:                   ; first call from DOS
    mov     cs:rh_seg,es        ; save request header ptr segment
    mov     cs:rh_ofs,bx        ; save request header ptr offset
    ret

; *******************************************************************
; * THE INTERRUPT PROCEDURE                                         *
; *******************************************************************

dev_interrupt:                  ; second call from DOS
    cld                         ; save machine state on entry
    push    ds
    push    es
    push    ax
    push    bx
    push    cx  
    push    dx
    push    di
    push    si

; perform branch based on the command passed in the req header

    mov     al,es:[bx]+2        ; get command code
    cmp     al,0                ; check for 0
    jnz     exit3               ; no - exit go to error exit
    rol     al,1                ; get offset into table
    lea     di,cmdtab           ; get address of command table
    mov     ah,0                ; clear hi order
    add     di,ax               ; add offset
    jmp     word ptr[di]        ; jump indirect

; command table
;       the command code field of the static request
;       field contains the function to be performed

cmdtab  label   byte            ;
        dw      init            ; initialization

; *******************************************************************
; *     LOCAL PROCEDURES                                            *
; *******************************************************************

initial proc    near
    lea     dx,msg1             ; initialization
    mov     ah,9                ; message
    int     21h                 ; dos call
    mov     al,30               ; number of seconds to wait
    call    sleep
    ret                         ; return
initial endp

; *******************************************************************
; *     DOS COMMAND PROCESSING                                      *
; *******************************************************************

;command    0   initialization

init:   call    initial         ; display a message
        lea     ax,exit         ; get end address (offset)
        mov     es:[bx]+0eh,ax  ; store offset address
        push    cs              ; get end
        pop     ax              ; address (segment)
        mov     es:[bx]+10h,ax  ; store in break address
        jmp     exit2

; *******************************************************************
; *     ERROR EXIT                                                  *
; *******************************************************************

; Set the done flag, error flag, and unknown command error code

exit3:  mov     es:word ptr 3[bx],8103h
        jmp     exit1                   ; restore environment

; *******************************************************************
; *     COMMON EXIT                                                 *
; *******************************************************************

; common exits fall thru code
;   2 sets status to done and no error
;   1 restore callers es:bx
;   0 restore machine state and exit

exit2:                                  ; set done flag and no error
        mov     es:word ptr 3[bx],0100h
exit1:  mov     bx,cs:rh_ofs            ; restore req hdr to bx and es
        mov     es,cs:rh_seg            ; as saved by dev_Strategy
exit0:  pop     si                      ; restore all registers
        pop     di
        pop     dx
        pop     cx
        pop     bx
        pop     ax
        pop     es
        pop     ds
        ret
exit:

; *******************************************************************
; *     END OF PROGRAM                                              *
; *******************************************************************


wait    endp

sleep proc    near

wait_for_al_seconds:      
wait_loop:
    push ax               ; save our counter (al)
    mov     [counter],al
loop_top:
    mov ah,2
    int 1ah             ; get time

    mov ah, [seconds]   ; retrieve last good value
    cmp ah, dh          ; is it same as last good value?
    jz  loop_top         ; yup, ignore it, loop again!

    mov [seconds], dh   ; save seconds

    ; display counter - can handle range of 0-99
    mov	al, [counter]   ; retrieve counter
    cbw                 ; set AH to 0
    mov  dl, 10
    div  dl             ; Divides AX by 10: quotient in al, remainder in ah
    add  ax, "00"
    mov  dx, ax         
    mov  ah, 02h        ; Display 1st digit of counter
    int  21h
    mov  dl, dh          
    int  21h            ; Display 2nd digit of counter

    
    lea     dx,crlf             ; display carriage return
    mov     ah,9                
    int     21h                 

    pop ax              
    dec al              ; decrease al by one (does not set flags!!)
    or al,al            ; set flags
    jnz wait_loop       ; al=0?  nope, around we go again!

    ret                 ; 
sleep   endp


cseg        ends
            end     begin

; that's all folks!


Now we can add line to C:\Windows\System32\config.nt to load our driver where we want it to pause:

DEVICE=%SystemRoot%\System32\wait.sys

To test all existing ntvdm.exe process must be terminated, as config.sys is only loaded when a new ntvdm.exe instance is created. Now when launching a 16-bit DOS application you will see a count down for 30 seconds when this line of config.nt has been hit:

Posted in Uncategorized | 1 Comment