1

I am currently trying to inject a code cave with thread injection to a remote win32 EXE running on my win7 (x64) system. To achieve this, I am using Microsoft VB6, through which I do the following:

  • OpenProcess, to get a handle to the remote process [OK]
  • VirtualAllocEx, to allocate some space inside of process (with PAGE_EXECUTE protection and MEM_COMMIT as parameter. lpAddress set to NULL so that the function determines where to allocate the region) [OK, returns a valid offset]
  • WriteProcessMemory, to write my shellcode [OK, actually writes the bytes correctly, I have checked with CheatEngine/MemoryView]
  • CreateRemoteThread, thread injection to execute my code cave [OK, returns a handle that is not NULL, my remote thread was -supposedly- successfully created]
  • My target EXE (host for the remote thread that was just created) crashes at this point ('exename' stopped working)
  • WaitForSingleObject / CloseHandle / VirtualFreeEx

For the sake of testing a successful code injection, I am trying to inject a shellcode that does nothing. I don't know much about shellcoding and asm, but I just started learning.

I've tried injecting different codes, like: - only NOPs (crashes but I'm assuming it's normal): \x90\x90\x90.. - only NULLs (same as above): \x00\x00\x00.. However what I don't understand is that NOPs followed by RETN crashes my target EXE aswell \x90\x90\x90\xCB. Every byte sequence I have tried injecting was followed by a NULL byte.

Why does my target process crash ? What byte sequence do I have to inject in order to perform a succesful injection that doesn't crash my target EXE (yet that doesn't do anything, just for the sake of testing the injection scheme) ?

What I want to end up doing is injecting a PUSH x, CALL targetfunction to a game. But if my dummy shellcode crashes my target process I'm assuming the latter byte sequence would too. Thanks for your time.

EDIT: The exception I get is 0xC0000005 [Access violation when writing]

VB6 Code: Just call the sub with the target Exe's pid as argument

Private Const PAGE_READWRITE As Long = &H4
Private Const PAGE_EXECUTE As Long = &H10

Private Const MEM_RELEASE As Long = &H8000
Private Const MEM_COMMIT As Long = &H1000
Private Const INFINITE As Long = &HFFFFFF

Public Const PROCESS_ALL_ACCESS As Long = &H1F0FFF

Public Declare Function OpenProcess Lib "kernel32" (ByVal dwDesiredAccess As Long, ByVal bInheritHandle As Long, ByVal dwProcessId As Long) As Long
Public Declare Function WriteProcessMemory Lib "kernel32" (ByVal hProcess As Long, ByVal lpBaseAddress As Any, lpBuffer As Any, ByVal nSize As Long, lpNumberOfBytesWritten As Long) As Long
Public Declare Function CloseHandle Lib "kernel32" (ByVal hObject As Long) As Long
Private Declare Function VirtualAllocEx Lib "kernel32" (ByVal hProcess As Long, ByVal lpAddress As Long, ByVal dwSize As Long, ByVal flAllocationType As Long, ByVal flProtect As Long) As Long
Private Declare Function VirtualFreeEx Lib "kernel32" (ByVal hProcess As Long, lpAddress As Any, ByVal dwSize As Long, ByVal dwFreeType As Long) As Long
Private Declare Function WaitForSingleObject Lib "kernel32" (ByVal hHandle As Long, ByVal dwMilliseconds As Long) As Long

Private Declare Function CreateRemoteThread Lib "kernel32" (ByVal hProcess As Long, lpThreadAttributes As Long, ByVal dwStackSize As Long, lpStartAddress As Long, lpParameter As Any, ByVal dwCreationFlags As Long, lpThreadId As Long) As Long

'Function that performs the shellcode injection to a remote process. Takes the target's PID as argument
Public Sub injectCode(ByVal lngPid As Long)
    Dim RemThread As Long, LngModule As Long, LngProcess As Long
    Dim i As Long

    'The byte sequence we'll inject in the remote process (
    Dim shellcode(4) As Byte
    shellcode(0) = &H90 'NOP : just to pretend that it's actual code
    shellcode(1) = &H90 'NOP : same
    shellcode(2) = &HC2 'Near return to calling procedure and pop 4 bytes from stack.
    shellcode(3) = &H4
    shellcode(4) = 0    'NULL terminator

    'OpenProcess, to get a handle to the remote process
    LngProcess = OpenProcess(PROCESS_ALL_ACCESS, False, lngPid)
    'to allocate some space inside of process (with PAGE_EXECUTE protection and MEM_COMMIT as parameter.
    'lpAddress set to NULL so that the function determines where to allocate the region)
    LngModule = VirtualAllocEx(LngProcess, 0, UBound(shellcode), MEM_COMMIT, PAGE_EXECUTE)

    Debug.Print "VirtualAllocEx: " & Hex(LngModule) 'debug info

    'writing our shellcode to the target's memory
    For i = 0 To UBound(shellcode)
        Call WriteProcessMemory(LngProcess, LngModule + i, shellcode(i), 1, 0&)
    Next i
    'thread injection to execute my code cave
    RemThread = CreateRemoteThread(LngProcess, 0&, 0&, ByVal LngModule, 0&, 0&, ByVal 0&)

    Debug.Print "CreateRemoteThread: " & Hex(RemThread) 'debug info

    'wait for the thread to run
    Call WaitForSingleObject(RemThread, INFINITE)
    CloseHandle (RemThread)
    Call VirtualFreeEx(LngProcess, LngModule, UBound(shellcode), MEM_RELEASE)

    Debug.Print "DONE" 'debug info
End Sub

Something weird happens though. When I put a MsgBox (that pauses the execution) for debug purposes BEFORE calling CreateRemoteThread, the function returns a non-NULL handle (but the target EXE crashes). And if I don't put a Msgbox before calling CreateRemoteThread, a NULL handle is returned.

Mike
  • 11
  • 2
  • `lpParameter As Any` is still wrong – Alex Guteniev Mar 04 '19 at 20:40
  • Hi Alexander, even when declaring lpParameter as Long (in the API declaration), the call to CreateRemoteThread returns 0. I don't really know how to fix this. I've read your comment mentioning the non-COM API but I haven't succeeded fixing the call :Private Declare Function CreateRemoteThread Lib "kernel32" (ByVal hProcess As Long, lpThreadAttributes As Long, ByVal dwStackSize As Long, lpStartAddress As Long, lpParameter As Long, ByVal dwCreationFlags As Long, lpThreadId As Long) As Long – Mike Mar 05 '19 at 21:11

1 Answers1

3

ThreadProc callback passed to CreateRemoteThread takes one LPVOID parameter and ThreadProc is __stdcall.

LPVOID parameter for ThreadProc is forwarded from CreateRemoteThread's LPVOID lpParameter.

On x86 it means that you need to put RET instruction such that frees four bytes from stack.

(Also ThreadProc returns DWORD, this means you should but return value to EAX before returning, but if you don't care about return value, you may skip it)

Additionally, CB is far return, you need to use near return.

Look for RETN imm opcode, looks like you need C2 04 00

Alex Guteniev
  • 12,039
  • 2
  • 34
  • 79
  • Thanks for your input ! I have tried injecting the following byte sequence: \x90\x90\xC2\x04\x00 (useless NOPs, just to pretend it's actual code) but that crashes too. From your answer, I think I understand everything I'll inject has to end that way (Near return to calling procedure and pop 4 bytes from stack, that correspond to the return address). However in my VB6 CreateRemoteThread call I don't really know what to pass as args. I've seen many LPTHREAD_START_ROUTINE while looking online but I can't seem to find anything of the like in VB6. – Mike Mar 03 '19 at 10:14
  • i'm performing the call that way in MS VB6: CreateRemoteThread(processHandle, 0, 0, remoteCodeOffset, 0, 0, 0). I thought ThreadProc was the callee (remote offset) – Mike Mar 03 '19 at 10:17
  • I've edited my answer to clarify what is passed as args. Still, don't think it would help, looks like you have issue with something else as well. – Alex Guteniev Mar 03 '19 at 10:23
  • You need to identify the exception, why exactly target exe stops working, using debugger, or from crash message, or by inspecting crash dump, or from event log. – Alex Guteniev Mar 03 '19 at 10:32
  • Here's what I get: exception code c0000005 [supposedly access violation, so my code is trying to access an address to which it does not have rights]. How does the code know where to return ? By poping 4 bytes off the stack and jumping to the previous eip ? Which leads to where, my VB caller app ? Sorry for those questions, I'm trying to understand but there are things that I fail to understand yet – Mike Mar 03 '19 at 10:41
  • Access violation has parameters, like reading/writing/executing and location, there may be some hint. – Alex Guteniev Mar 03 '19 at 10:43
  • Functions like `CreateThread` or `CreateRemoteThread` calls user function from a wrapper, so there is a place to return to. In case of `CreateRemoteThread`, this place is in remote process. (Ultimately a thread would call `NtTerminateThread`, but is is done inside system part that wraps your `ThreadProc`) – Alex Guteniev Mar 03 '19 at 10:44
  • 1
    Please post your VB6 code. I followed steps, and it works fine for me. – Alex Guteniev Mar 03 '19 at 11:31
  • You should be able to tell what address the access violation is at. Is it the address of your injected code? – prl Mar 03 '19 at 11:31
  • You're right, there must be something wrong with my VB6 code. I have tried a python code injector https://gist.github.com/andreafortuna/20d19ac02393264930b3d331fe66a6f6 and it's working with the byte sequence \xc2\x04\x00 so the error might come from my vb implementation. What I fail to grasp, however, is that I call CreateRemoteThread the same way it is done in the python script provided above. Same number of args, same types.. I don't know what to do to make it work in VB. Thanks a lot for the information you've provided ! – Mike Mar 03 '19 at 11:44
  • Maybe the VB wrapper for CreateRemoteThread has an additional parameter or different order for some reason? – prl Mar 03 '19 at 11:50
  • I think that the difference is still in code. "\xc2\x04\x00" is string of 3 bytes: `C2 04 00` in both C and Python. But I'm not sure about Visual Basic. If it is literally "\xc2\x04\x00", that is byte for \ , byte for x, byte for c, and so on, it will for sure won't work. – Alex Guteniev Mar 03 '19 at 11:54
  • Here is my VB6 code: https://pastebin.com/4LcTSA10 Reminder: I'm using the same number of args as the python version from GitHub (earlier comment). Thanks for helping me out. Almost there. – Mike Mar 03 '19 at 12:22
  • Relevant declarations: https://pastebin.com/VZCztMC9 (Sorry, I forgot to include them in my previous comment) – Mike Mar 03 '19 at 12:24
  • `As Any` parameter type should not be used for non-COM API's such as `CreateRemoteThread`. Pass remaining parameters `ByVal As Long` either. – Alex Guteniev Mar 03 '19 at 12:32