One of my favorited things about MASM is that it has the ability to inject raw functions in its own static code into another process without the need for a DLL. This code is the first step to injecting code into a process but I plan to build on top of this so that the code is self contained and does not require an external DLL to be loaded via Kernel LoadLibrary.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 |
; Declare the 'main' procedure as public, making it the entry point for the linker. public main ; Declare external procedures that reside in Windows DLLs (like kernel32.dll, user32.dll) ; or the C runtime library (for strcmp). The linker will resolve these addresses. ; ': proc' specifies that these are procedure labels. extern MessageBoxA : proc ; USER32.DLL: Displays a message box. extern OpenProcess : proc ; KERNEL32.DLL: Opens an existing process object. extern VirtualAllocEx : proc ; KERNEL32.DLL: Reserves or commits memory in another process. extern CreateRemoteThread : proc ; KERNEL32.DLL: Creates a thread that runs in another process. extern CloseHandle : proc ; KERNEL32.DLL: Closes an open object handle. extern WriteProcessMemory : proc ; KERNEL32.DLL: Writes data to memory in another process. extern GetModuleHandleA : proc ; KERNEL32.DLL: Retrieves a module handle for a specified module (if loaded). extern GetProcAddress : proc ; KERNEL32.DLL: Retrieves the address of an exported function in a DLL. extern CreateToolhelp32Snapshot : proc ; KERNEL32.DLL: Takes a snapshot of specified processes, heaps, modules, threads. extern Process32First : proc ; KERNEL32.DLL: Retrieves information about the first process in a snapshot. extern Process32Next : proc ; KERNEL32.DLL: Retrieves information about the next process in a snapshot. extern strcmp : proc ; MSVCRT.DLL (usually): Compares two C strings. ; Section for initialized data. .data ; Define null-terminated strings for message box titles and content. msg_title db "x64 dll injector", 0; Window title for message boxes. msg_not_found db "Process is not running.", 0 ; Error message if target process isn't found. msg_cant_inject db "Injection failed.", 0 ; Error message if injection steps fail. msg_success db "Injection succeded.", 0 ; Success message. ; Define null-terminated strings used for API calls. kernel32 db "kernel32.dll", 0 ; Name of the core Windows library. load_library db "LoadLibraryA", 0 ; Name of the function used to load DLLs. target_process db "notepad.exe", 0 ; The executable name of the process to inject into. library_name db "C:\\DllToInject.dll", 0 ; --- IMPORTANT: Full path to the DLL that will be injected. --- ; --- Ensure this path is correct and accessible! --- ; Calculate the length of the library_name string *excluding* the null terminator. ; '$' represents the current address, so '$ - library_name' gives the length. library_len equ $ - library_name ; Section for executable code. .code ; ============================================================================ ; find_process PROC ; Purpose: Finds the Process ID (PID) of the 'target_process' by iterating ; through a snapshot of all running processes. ; Input: None (uses global 'target_process' variable). ; Output: EAX = PID if found, 0 otherwise. ; Uses: CreateToolhelp32Snapshot, Process32First, Process32Next, CloseHandle, strcmp ; Clobbers: RAX, RCX, RDX, R8-R11 (as per x64 ABI), R12, R13 (saved/restored) ; ============================================================================ find_process proc ; Standard x64 function prologue: Save non-volatile registers that will be used. ; R12-R15 must be preserved across function calls by the callee. push r12 ; Save R12 (will be used for snapshot handle). push r13 ; Save R13 (will be used for the found PID). ; Save the base pointer and set up the new stack frame base. push rbp mov rbp, rsp ; Allocate stack space for local variables and shadow space. ; Needs space for PROCESSENTRY32 struct (~304 bytes = 130h) + shadow space (32 bytes = 20h). ; 150h is allocated, providing some extra room. sub rsp, 150h ; Align the stack pointer to a 16-byte boundary. Required by the x64 ABI before making CALLs. and rsp, -10h ; Initialize the 'found PID' register (R13) to 0. xor r13, r13 ; Prepare the PROCESSENTRY32 structure on the stack. Its address will be [rsp + 20h]. ; The first member (dwSize) must be set before calling Process32First/Next. ; sizeof(PROCESSENTRY32) is 304 bytes (0x130). mov qword ptr [rsp + 20h], 130h ; Set pe32.dwSize = 304. (Note: QWORD mov used, but only DWORD needed). ; Call CreateToolhelp32Snapshot to get a snapshot of running processes. ; Arguments (x64 ABI): RCX, RDX, R8, R9, then stack. mov rcx, 2 ; RCX = dwFlags = TH32CS_SNAPPROCESS (snapshot processes). xor rdx, rdx ; RDX = th32ProcessID = 0 (snapshot all processes). call CreateToolhelp32Snapshot ; Return value (handle or INVALID_HANDLE_VALUE) in RAX. mov r12, rax ; Store the snapshot handle in R12. cmp r12, -1 ; Compare handle with INVALID_HANDLE_VALUE (-1). je exit ; If failed (-1), jump to the exit routine. ; Call Process32First to retrieve information about the first process. mov rcx, r12 ; RCX = hSnapshot (handle from CreateToolhelp32Snapshot). lea rdx, [rsp + 20h] ; RDX = lpProcessEntry (pointer to our buffer on the stack). call Process32First ; Returns non-zero on success, 0 on failure/end. cmp rax, 0 ; Check the return value in RAX. je exit_cleanup ; If failed (0), jump to cleanup (no processes found or error). ; Loop through the processes in the snapshot. process_loop: ; Compare the current process name with our target process name. lea rcx, target_process ; RCX = pointer to the target process name ("notepad.exe"). ; Calculate address of szExeFile within PROCESSENTRY32 on stack: [rsp + 20h + offset]. ; Offset(szExeFile) = 0x2C (44 bytes). See PROCESSENTRY32 structure definition. lea rdx, [rsp + 20h + 2Ch] ; RDX = pointer to pe32.szExeFile on the stack. call strcmp ; Call C string comparison function. Returns 0 if strings match. cmp rax, 0 ; Check if strcmp returned 0. je found ; If strings are equal (0), jump to the 'found' routine. ; If not found, get the next process in the snapshot. mov rcx, r12 ; RCX = hSnapshot. lea rdx, [rsp + 20h] ; RDX = lpProcessEntry (pointer to buffer). call Process32Next ; Returns non-zero on success, 0 on failure/end of list. cmp rax, 0 ; Check the return value. jne process_loop ; If successful (non-zero), loop back to check the next process. ; If Process32Next returns 0, we've checked all processes or an error occurred. ; Now, clean up the snapshot handle. exit_cleanup: mov rcx, r12 ; RCX = handle to close. call CloseHandle ; Close the snapshot handle to free resources. ; Prepare to return from the function. exit: mov eax, r13d ; Move the found PID (from lower 32 bits of R13) into EAX (return value). ; Standard x64 function epilogue: Restore stack and saved registers. mov rsp, rbp ; Deallocate local stack space. pop rbp ; Restore the base pointer. pop r13 ; Restore original R13 value. pop r12 ; Restore original R12 value. ret ; Return control to the caller. ; This code block is executed when strcmp finds a match. found: ; Extract the Process ID (th32ProcessID) from the PROCESSENTRY32 structure. ; Offset(th32ProcessID) = 0x8. mov r13d, dword ptr [rsp + 20h + 8h] ; Copy PID from [rsp + 20h + 8h] into R13D (lower 32 bits of R13). jmp exit_cleanup ; Jump to close the snapshot handle and return the PID. find_process endp ; ============================================================================ ; get_load_library PROC ; Purpose: Retrieves the memory address of the LoadLibraryA function from kernel32.dll. ; Input: None. ; Output: RAX = Address of LoadLibraryA if successful, 0 otherwise. ; Uses: GetModuleHandleA, GetProcAddress ; Clobbers: RAX, RCX, RDX, R8-R11 (as per x64 ABI) ; ============================================================================ get_load_library proc ; Standard x64 function prologue. push rbp mov rbp, rsp ; Allocate shadow space (32 bytes) required for calling functions. sub rsp, 20h ; Ensure stack alignment (may be redundant if RSP was already aligned). and rsp, -10h ; Get a handle to kernel32.dll (it's already loaded in almost any process). lea rcx, kernel32 ; RCX = lpModuleName = pointer to "kernel32.dll". call GetModuleHandleA ; Returns module handle in RAX, or NULL on failure. ; Get the address of the LoadLibraryA function within kernel32.dll. mov rcx, rax ; RCX = hModule (handle from GetModuleHandleA). lea rdx, load_library ; RDX = lpProcName = pointer to "LoadLibraryA". call GetProcAddress ; Returns function address in RAX, or NULL on failure. ; Address of LoadLibraryA (or NULL) is now in RAX, ready to be returned. ; Standard x64 function epilogue. mov rsp, rbp ; Deallocate shadow space. pop rbp ; Restore base pointer. ret ; Return control to the caller. get_load_library endp ; ============================================================================ ; inject_image PROC ; Purpose: Injects the specified DLL ('library_name') into the target process. ; Input: RCX = Process ID (PID) of the target process. ; Output: RAX = 1 if injection succeeded, 0 otherwise. ; Uses: OpenProcess, VirtualAllocEx, WriteProcessMemory, get_load_library, ; CreateRemoteThread, CloseHandle ; Clobbers: RAX, RCX, RDX, R8-R11 (as per x64 ABI), R12, R13, R14 (saved/restored) ; ============================================================================ inject_image proc ; Standard x64 prologue: Save non-volatile registers used. push r12 ; Save R12 (will be used for process handle). push r13 ; Save R13 (will be used for allocated memory pointer). push r14 ; Save R14 (will be used for injection status flag). push rbp mov rbp, rsp ; Allocate stack space for arguments passed on the stack to API calls ; (e.g., 5th/6th/7th args for VirtualAllocEx/WriteProcessMemory/CreateRemoteThread) ; Needs 3*8=24 bytes (18h) for CRT args + 32 bytes (20h) shadow space = 38h minimum. sub rsp, 38h ; Align stack to 16-byte boundary. and rsp, -10h ; Initialize injection status flag (R14) to 0 (failure). Will be set to 1 on success. xor r14, r14 ; Save the target PID (passed in RCX) into a non-volatile register (R12). mov r12, rcx ; Call OpenProcess to get a handle to the target process. mov rcx, 1FFFFFh ; RCX = dwDesiredAccess = PROCESS_ALL_ACCESS (request full permissions). xor rdx, rdx ; RDX = bInheritHandle = FALSE (handle is not inheritable). mov r8 , r12 ; R8 = dwProcessId = The PID of the target process. call OpenProcess ; Returns process handle in RAX, or NULL on failure. mov r12, rax ; Store the process handle in R12 (overwriting the PID). ; Check if OpenProcess failed. It returns NULL (0) on failure. ; Note: Original code compared to -1, which is technically incorrect but might work ; due to sign extension. Comparing to 0 is more robust. For commenting, we follow the code. cmp r12, -1 ; Check if handle is INVALID_HANDLE_VALUE (-1) or NULL. je exit ; If failed, jump to the final exit. ; Allocate memory within the target process's address space for the DLL path. mov rcx, r12 ; RCX = hProcess (handle from OpenProcess). xor rdx, rdx ; RDX = lpAddress = NULL (let the system choose the address). mov r8 , library_len ; R8 = dwSize = Size needed for the DLL path string. mov r9 , 3000h ; R9 = flAllocationType = MEM_COMMIT | MEM_RESERVE (0x1000 | 0x2000). ; 5th argument goes on the stack (at RSP+20h in the caller's frame). mov qword ptr [rsp + 20h], 4 ; StackArg1 = flProtect = PAGE_READWRITE (0x4). call VirtualAllocEx ; Returns pointer to allocated memory in RAX, or NULL on failure. mov r13, rax ; Store the allocated memory pointer in R13. cmp r13, 0 ; Check if VirtualAllocEx returned NULL (0). je exit_cleanup ; If failed, jump to cleanup (close process handle). ; Write the DLL path string into the allocated memory in the target process. mov rcx, r12 ; RCX = hProcess. mov rdx, r13 ; RDX = lpBaseAddress (pointer returned by VirtualAllocEx). lea r8 , library_name ; R8 = lpBuffer (pointer to our local DLL path string). mov r9 , library_len ; R9 = nSize (length of the string to write). ; 5th argument on the stack. mov qword ptr [rsp + 20h], 0; StackArg1 = lpNumberOfBytesWritten = NULL (optional, don't need the value). call WriteProcessMemory ; Returns non-zero on success, 0 on failure. cmp rax, 0 ; Check if WriteProcessMemory returned 0. je exit_cleanup ; If failed, jump to cleanup. ; Get the address of LoadLibraryA. This address is the same in the target process ; because kernel32.dll is loaded at the same base address in all processes (usually). call get_load_library ; Returns address in RAX. cmp rax, 0 ; Check if get_load_library failed (returned NULL). je exit_cleanup ; If failed, jump to cleanup. ; Create a new thread in the target process. This thread will start execution ; at the address of LoadLibraryA, and will be passed the address of the DLL ; path (which we wrote into the target process) as its parameter. ; Effectively calls: LoadLibraryA(address_of_dll_path_in_target_process) mov rcx, r12 ; Arg1: hProcess (target process handle). xor rdx, rdx ; Arg2: lpThreadAttributes (NULL = default). xor r8 , r8 ; Arg3: dwStackSize (0 = default). mov r9 , rax ; Arg4: lpStartAddress (address of LoadLibraryA). ; Arguments 5, 6, 7 are passed on the stack. mov qword ptr [rsp + 20h], r13 ; Arg5: lpParameter (address of the allocated DLL path string). mov qword ptr [rsp + 28h], 0 ; Arg6: dwCreationFlags (0 = run immediately). mov qword ptr [rsp + 30h], 0 ; Arg7: lpThreadId (NULL = don't need the ID). call CreateRemoteThread ; Returns handle to the new thread in RAX, or NULL on failure. ; We don't usually need to interact with the remote thread further, so close its handle. mov rcx, rax ; RCX = hHandle (the thread handle returned by CreateRemoteThread). call CloseHandle ; Close the handle. (Check for NULL handle before calling ideally). ; If we reached here, the remote thread was likely created successfully. mov r14, 1 ; Set the injection status flag (R14) to 1 (success). ; Cleanup routine: Close the handle to the target process. exit_cleanup: mov rcx, r12 ; RCX = hHandle (process handle stored in R12). call CloseHandle ; Close the handle. (Check for NULL handle before calling ideally). ; Final exit point for the procedure. exit: mov rax, r14 ; Move the injection status flag (R14) into RAX (return value). ; Standard x64 function epilogue. mov rsp, rbp ; Deallocate local stack space. pop rbp ; Restore base pointer. pop r14 ; Restore original R14 value. pop r13 ; Restore original R13 value. pop r12 ; Restore original R12 value. ret ; Return control to the caller. inject_image endp ; ============================================================================ ; main PROC ; Purpose: Main entry point of the injector program. ; Coordinates finding the process and injecting the DLL. ; Displays success or failure messages. ; Input: None (standard program entry). ; Output: None (exits program, returns 0 via RET implicitly if needed). ; Uses: find_process, inject_image, MessageBoxA ; Clobbers: RAX, RCX, RDX, R8-R11 (as per x64 ABI) ; ============================================================================ main proc ; Standard x64 function prologue. push rbp mov rbp, rsp ; Allocate shadow space for function calls within main. sub rsp, 20h ; Align stack. and rsp, -10h ; Call the find_process function to get the target process PID. call find_process ; Returns PID or 0 in EAX. cmp rax, 0 ; Check if the PID is 0. je process_not_found ; If 0, jump to the 'process_not_found' error handling. ; Process found, PID is in RAX. Prepare to call inject_image. mov rcx, rax ; Move PID from RAX into RCX (first argument for inject_image). call inject_image ; Call the injection function. Returns 1 (success) or 0 (fail) in RAX. cmp rax, 0 ; Check if injection failed (returned 0). je injection_fail ; If 0, jump to the 'injection_fail' error handling. ; If inject_image returned non-zero (1), injection succeeded. Show success message. xor rcx, rcx ; RCX = hWnd = NULL (no owner window). lea rdx, msg_success ; RDX = lpText = pointer to success message string. lea r8 , msg_title ; R8 = lpCaption = pointer to title string. mov r9 , 40h ; R9 = uType = MB_OK | MB_ICONINFORMATION (0x40). call MessageBoxA ; Display the message box. jmp exit ; Jump to the end of the main function. ; Error handling block: Target process was not found. process_not_found: xor rcx, rcx ; RCX = hWnd = NULL. lea rdx, msg_not_found ; RDX = lpText = pointer to "not found" message. lea r8 , msg_title ; R8 = lpCaption = pointer to title string. ; Note: 30h is MB_OK | MB_ICONWARNING. Could use MB_ICONERROR (10h) instead. mov r9 , 30h ; R9 = uType = MB_OK | MB_ICONWARNING (0x30). call MessageBoxA ; Display the message box. jmp exit ; Jump to the end of the main function. ; Error handling block: DLL injection failed at some step. injection_fail: xor rcx, rcx ; RCX = hWnd = NULL. lea rdx, msg_cant_inject ; RDX = lpText = pointer to "injection failed" message. lea r8 , msg_title ; R8 = lpCaption = pointer to title string. mov r9 , 30h ; R9 = uType = MB_OK | MB_ICONWARNING (0x30). call MessageBoxA ; Display the message box. ; Exit point for the main function. exit: ; Standard x64 function epilogue. mov rsp, rbp ; Deallocate local stack space. pop rbp ; Restore the base pointer. ret ; Return (effectively exits the program). main endp ; Directive indicating the end of the assembly source file. end |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
@echo off setlocal REM --- Configuration --- REM Set the base path for your MASM64 installation if it's not in the system PATH SET MASM_PATH=\masm64 REM Set the path to your Windows SDK libraries (adjust if necessary) REM Common locations might be within Program Files (x86)\Windows Kits\10\Lib\ REM Or sometimes included with the assembler/linker distribution. REM If PoLink finds them automatically, you might not need this explicit path. SET SDK_LIB_PATH=%MASM_PATH%\lib64 REM Or potentially a full path like: "C:\Program Files (x86)\Windows Kits\10\Lib\10.0.22621.0\um\x64" SET LIB_FILES=\masm64\lib64\kernel32.lib \masm64\lib64\user32.lib \masm64\lib64\msvcrt.lib REM --- Cleanup --- if exist "InjectSpeed.obj" del "InjectSpeed.obj" if exist "InjectSpeed.exe" del "InjectSpeed.exe" REM --- Assemble --- echo Assembling InjectSpeed.asm... "%MASM_PATH%\bin64\ml64.exe" /c "InjectSpeed.asm" /Fo"InjectSpeed.obj" if errorlevel 1 ( echo. echo *** Assembly Error *** goto TheEnd ) REM --- Link --- echo Linking InjectSpeed.obj... REM PoLink typically takes libraries directly. Add /LIBPATH if needed. "%MASM_PATH%\bin64\PoLink.exe" /SUBSYSTEM:CONSOLE /ENTRY:main "InjectSpeed.obj" %LIB_FILES% /OUT:"InjectSpeed.exe" REM If PoLink complains about /LIBPATH or finds libs automatically, you might remove /LIBPATH:"%SDK_LIB_PATH%" REM Or if PoLink is different, check its documentation for library path syntax. if errorlevel 1 ( echo. echo *** Link Error *** goto TheEnd ) REM --- Success --- echo. echo Build successful! dir "InjectSpeed.*" echo. REM --- Optional: Copy and Run --- echo Running InjectSpeed.exe... InjectSpeed.exe :TheEnd echo. endlocal |