Versión: 3.x.
Descargas: código de fuente, archivo ejecutable.
Código de fuente de un inyector de DLL de 32-bit para Windows. Incluye una lista de procesos con ruta completa (modificable) e íconos de los respectivos procesos. Utiliza intensivamente ctypes
para acceder a la API de Windows, Pillow (PIL) para soportar transparencia en los íconos de la lista de procesos y Tcl/Tk (tkinter
) para la interfaz gráfica.
Consta únicamente de dos archivos: main.py
, archivo principal, e injector.py
, módulo encargado de obtener la lista de procesos e inyectar archivos DLL (también disponible para Python 2 en este enlace).
Vista previa del archivo principal (descarga del código completo al principio del artículo):
#!/usr/bin/env python # -*- coding: utf-8 -*- import tkinter as tk from tkinter import ttk, messagebox, PhotoImage from tkinter.filedialog import askopenfilename from tkinter.font import Font, nametofont from ctypes import byref, memset, sizeof, windll, Structure from ctypes import create_string_buffer, create_unicode_buffer from ctypes import c_int, c_void_p from ctypes.wintypes import WORD, DWORD, BYTE, BOOL, LONG, HBITMAP from traceback import print_exc from base64 import b64decode from PIL import Image, ImageDraw, ImageTk from injector import (OpenProcess, CloseHandle, get_processes_list, inject_library) # Funciones, constantes y estructuras de la API de Windows. PROCESS_QUERY_LIMITED_INFORMATION = 0x1000 BI_RGB = 0 DIB_RGB_COLORS = 0 QueryFullProcessImageName = windll.kernel32.QueryFullProcessImageNameW ExtractIconEx = windll.shell32.ExtractIconExW DestroyIcon = windll.user32.DestroyIcon GetIconInfo = windll.user32.GetIconInfo GetDC = windll.user32.GetDC ReleaseDC = windll.user32.ReleaseDC DeleteDC = windll.gdi32.DeleteDC SelectObject = windll.gdi32.SelectObject CreateCompatibleDC = windll.gdi32.CreateCompatibleDC GetPixel = windll.gdi32.GetPixel GetDIBits = windll.gdi32.GetDIBits # Imagen por defecto para procesos sin íconos, codificada vía base64 # para distribuir sin dependencias. PROCESS_DEFAULT_ICON = b64decode(b'''\ iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9\ iZSBJbWFnZVJlYWR5ccllPAAAAsVJREFUeNpcU91LFFEU/83Ox864u64flfaBlFGgCRERYQ\ k+WBFERdZDf0AQQmAf70Fvvkg9BIIg9BAUZEqIESVCL0KiiA89GJUgGoruurgzO1937u3cc\ XexBn7Mvff8fuece+45ihACL8Z+47+vi9BH6CQcI6wQZglDhOmHva1VoiIdPH9fddBAeGpa\ Rn/D/iz0pAFVV8FZhNAPkN/cgVvyXxJn4NHt1rWqg8HRX3KdVRQUGg/UwcpkUHI5uFCgJAD\ BiagIpCwVvl3E5kYBpGt9cuf4ckIqORcSz+obs0gYaRSKERyPw3YZLuz7A7vEUKJ9wWYQWg\ oyCPEfS23sIOLoonT7VTNN5AhnG7dQp7mIGIdlWoiIkKV9RzZHjhmgp5C0zAcDb3/e2s1Ai\ L7a+lrsUGTPZ1jKp3HxaICmFINu6PG/s8XH0lYKnhsRj0HypU7bzUB0R9ARhBEYRVvdVjAj\ TPS0KbAMA90nGD59t5BzFBlO3h9hUpe6zoqDw2FIBRMMd0/bMEhkmmYMVVWh6wy9Zzz4voc\ gCPBmIQ0/0CCAdOyAR1gPQ9bs+gKv52ri9zzSwHHzXAIZQ4MfAhOLAqt5SdfiLKJIgIXCLR\ dRTIeBT6nRm9MVmjMRLrUreDdTpOqHGPtWxJVTCTSleWznPAEWeLGu4mDYsW2oWiKueMfBC\ B/mAqzlQiqqh+WNAJMLPjoOsdiuahpKjiN1I5VX+Oo47ghnJSQ0C5OLCta3ZTQqVhDG/5VN\ jo90Lu3gHopFZ3zgXtt4pQ8khvK5bTK60JI1UFQ9rvbQZ486kcd7eS7F+Vwu5lcbqdyJ85T\ eeTK+cu0ctS49rFkDjZpLt9JQqHCekyfx1ihjvGfwfvuX6ixMTU39M4oTP5qvlafxMsGQSR\ KkYPj6yfXxvVxtz7q9AiK1l8fYKNtUwlVCC+EGYb483rN/BRgAM8iDXy98gZ8AAAAASUVOR\ K5CYII=''') class ICONINFO(Structure): _fields_ = [ ("fIcon", BOOL), ("xHotspot", DWORD), ("yHotspot", DWORD), ("hbmMask", HBITMAP), ("hbmColor", HBITMAP) ] class RGBQUAD(Structure): _fields_ = [ ("rgbBlue", BYTE), ("rgbGreen", BYTE), ("rgbRed", BYTE), ("rgbReserved", BYTE), ] class BITMAPINFOHEADER(Structure): _fields_ = [ ("biSize", DWORD), ("biWidth", LONG), ("biHeight", LONG), ("biPlanes", WORD), ("biBitCount", WORD), ("biCompression", DWORD), ("biSizeImage", DWORD), ("biXPelsPerMeter", LONG), ("biYPelsPerMeter", LONG), ("biClrUsed", DWORD), ("biClrImportant", DWORD) ] class BITMAPINFO(Structure): _fields_ = [ ("bmiHeader", BITMAPINFOHEADER), ("bmiColors", RGBQUAD * 1), ] class Checkbox(ttk.Checkbutton): """ Una implementación más agradable alrededor de ttk.Checkbutton. """ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.variable = tk.IntVar(self) self.configure(variable=self.variable) def checked(self): return bool(self.variable.get()) def check(self): self.variable.set(1) def uncheck(self): self.variable.set(0) class Linkbutton(ttk.Button): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) # Obtener el nombre de la fuente utilizada para ttk.Label. label_font = nametofont("TkDefaultFont").cget("family") self.font = Font(family=label_font, size=9) # Crear un nuevo estilo. style = ttk.Style() style.configure( "Link.TLabel", foreground="#357fde", font=self.font) # Aplicarlo al enlace. self.configure(style="Link.TLabel", cursor="hand2") # Configurar eventos. self.bind("<Enter>", self.on_mouse_enter) self.bind("<Leave>", self.on_mouse_leave) def on_mouse_enter(self, event): # Aplicar subrayado. self.font.configure(underline=True) def on_mouse_leave(self, event): # Quitar subrayado. self.font.configure(underline=False) def bgra_to_rgba(data): """Convertir de formato BGRA (Windows) a RGBA (Pillow).""" mutable_data = bytearray(data) for i in range(0, len(data), 4): # Obtener los valores originales de rojo y azul. b = data[i] r = data[i + 2] # Intercambiarlos. mutable_data[i] = ord(r) mutable_data[i + 2] = ord(b) # Convertir nuevamente a un tipo inmutable. return bytes(mutable_data) class Application(ttk.Frame): def __init__(self, main_window): super().__init__(main_window) main_window.title("Inyector de DLL") self.filename = None self.processes = {} self.icons = {} # Expandir automáticamente los controles. main_window.rowconfigure(0, weight=1, minsize=310) main_window.columnconfigure(0, weight=1, minsize=455) self.grid(row=0, column=0, sticky="nsew") self.create_widgets() def create_widgets(self): """Crear todos los controles.""" self.lbl_filename = ttk.Label( self, text="No se ha seleccionado ningún archivo.") self.lbl_filename.grid( row=0, column=0, padx=5, pady=5, sticky="nsew") self.default_icon = tk.PhotoImage(data=PROCESS_DEFAULT_ICON) self.btn_browse = ttk.Button( self, text="Examinar", command=self.browse) self.btn_browse.grid( row=0, column=1, padx=5, pady=5, sticky="nsew") self.tree_processes = ttk.Treeview( self, columns=("pid",), selectmode=tk.BROWSE) self.tree_processes.heading("#0", text="Nombre") self.tree_processes.heading("pid", text="PID") self.tree_processes.column("pid", width=70, stretch=False) self.tree_processes.grid( row=1, column=0, columnspan=2, padx=5, sticky="nsew") # Crear una barra de deslizamiento para la lista de procesos. scrollbar = ttk.Scrollbar( self.tree_processes, orient=tk.VERTICAL) scrollbar.pack(side=tk.RIGHT, fill=tk.Y) # Añadirla. self.tree_processes.config(yscrollcommand=scrollbar.set) scrollbar.config(command=self.tree_processes.yview) self.chk_fullpath = Checkbox(self, text="Ruta completa", command=self.populate_processes_list) self.chk_fullpath.check() self.chk_fullpath.grid( row=2, column=0, padx=5, pady=5, sticky="ew") self.lnk_refresh = Linkbutton(self, text="Actualizar lista", command=self.refresh_processes) self.lnk_refresh.grid( row=2, column=1, padx=5) self.btn_inject = ttk.Button(self, text="Inyectar", command=self.inject) self.btn_inject.grid( row=3, column=0, columnspan=2, sticky="ew", pady=5, padx=5) # Expansión automática. self.rowconfigure(1, weight=1) self.columnconfigure(0, weight=1) # Obtener los procesos por primera vez. self.refresh_processes() def inject(self): """Determinar que el archivo y el proceso seleccionados sean correctos y, en caso afirmativo, proceder con la inyección. """ if not self.filename: messagebox.showinfo("Inyector de DLL", "Debes seleccionar un archivo.") return selection = self.tree_processes.selection() if not selection: messagebox.showinfo("Inyector de DLL", "Debes seleccionar un proceso de la lista.") else: item = self.tree_processes.item(selection[0]) pid = item["values"][0] try: inject_library(bytes(self.filename, "utf-8"), pid) except RuntimeError: messagebox.showerror("Operación fallida", "No se ha podido inyectar el archivo.") # Registrar el error completo en un archivo. with open("dllinjector.log", "a") as f: print_exc(file=f) else: messagebox.showinfo("Operación exitosa", "El archivo ha sido inyectado correctamente.") def load_processes(self): """Obtener información de los procesos.""" self.processes = {} dc = CreateCompatibleDC(0) if dc == 0: raise RuntimeError("Failed while creating DC.") for proc_name, pid in get_processes_list(): # Obtener acceso al proceso. proc_handle = OpenProcess( PROCESS_QUERY_LIMITED_INFORMATION, False, pid ) # Almacenar un objeto para guardar la ruta completa del # proceso. wbuffer = create_unicode_buffer(500) size = c_int(500) full_path = QueryFullProcessImageName( proc_handle, 0, byref(wbuffer), byref(size) ) # Los archivos del sistema no tienen una ruta completa. if full_path: proc_name = wbuffer.value else: proc_name = proc_name.decode("utf-8") CloseHandle(proc_handle) # Obtener el ícono del proceso. icon = c_int(0) ExtractIconEx(proc_name, 0, None, byref(icon), 1) if icon.value > 0: icon_info = ICONINFO(0, 0, 0, 0, 0) if GetIconInfo(icon, byref(icon_info)): # Tamaño. w, h = 16, 16 bmi = BITMAPINFO() memset(byref(bmi), 0, sizeof(bmi)) bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER) bmi.bmiHeader.biWidth = w; bmi.bmiHeader.biHeight = -h; bmi.bmiHeader.biPlanes = 1; bmi.bmiHeader.biBitCount = 32; bmi.bmiHeader.biCompression = BI_RGB; bmi.bmiHeader.biSizeImage = w * h * 4; data = create_string_buffer(bmi.bmiHeader.biSizeImage) # ImageTk es utilizado para soportar la transparencia # (el canal Alpha) de los íconos. if GetDIBits(dc, icon_info.hbmColor, 0, h, data, byref(bmi), DIB_RGB_COLORS): data = bgra_to_rgba(data) image = ImageTk.PhotoImage( Image.frombytes("RGBA", (w, h), data), (w, h) ) self.icons[pid] = image DestroyIcon(icon) self.processes[pid] = proc_name DeleteDC(dc) def populate_processes_list(self): """Cargar los procesos en la lista.""" # Eliminar los procesos anteriores. self.tree_processes.delete(*self.tree_processes.get_children()) for pid, proc_name in self.processes.items(): if not self.chk_fullpath.checked(): backslash = proc_name.rfind("\\") if backslash > -1: proc_name = proc_name[backslash + 1:] self.tree_processes.insert( "", tk.END, text=proc_name, values=(pid,), image=self.icons.get(pid, self.default_icon) ) def refresh_processes(self): """Actualizar la lista de procesos.""" self.load_processes() self.populate_processes_list() def browse(self): """Abrir el diálogo para buscar un archivo.""" filename = askopenfilename( filetypes=(("Windows DLL", "*.dll"),)) if filename: self.lbl_filename["text"] = filename self.filename = filename main_window = tk.Tk() app = Application(main_window) app.mainloop()
Curso online 👨💻
¡Ya lanzamos el curso oficial de Recursos Python en Udemy!
Un curso moderno para aprender Python desde cero con programación orientada a objetos, SQL y tkinter
en 2024.
Consultoría 💡
Ofrecemos servicios profesionales de desarrollo y capacitación en Python a personas y empresas. Consultanos por tu proyecto.
Larry says:
Hola estoy tratando de ejecutar el codigo y me da error en estas lineas
PS H:\Archivos\DLLINYECTOR\code> python main.py
Traceback (most recent call last):
File «H:\Archivos\DLLINYECTOR\code\main.py», line 342, in
app = Application(main_window)
File «H:\Archivos\DLLINYECTOR\code\main.py», line 157, in __init__
self.create_widgets()
File «H:\Archivos\DLLINYECTOR\code\main.py», line 211, in create_widgets
self.refresh_processes()
File «H:\Archivos\DLLINYECTOR\code\main.py», line 331, in refresh_processes
self.load_processes()
File «H:\Archivos\DLLINYECTOR\code\main.py», line 249, in load_processes
for proc_name, pid in get_processes_list():
File «H:\Archivos\DLLINYECTOR\code\injector.py», line 79, in GetProcessesList
raise RuntimeError(«Could not get processes list.»)
RuntimeError: Could not get processes list.
Recursos Python says:
Hola, ¿lo ejecutaste como administrador?
Saludos
larson says:
no se sabe cual es la finalidad de inyectar dll a los procesos de Windows no explica nada el post aunque parece interesante me gustaría saber cual es el beneficio de inyectar esas DLL a los procesos alguien puede explicar Por Favor
Recursos Python says:
Hola larson. La inyección de DLL es un método para inyectar código en procesos ajenos, y así poder modificar el comportamiento de programas de terceros en tiempo real. Pero aquí solo nos ocupamos del programa responsable de realizar la inyección de un archivo DLL en un proceso, cosa que se puede hacer en Python, que es el tema de esta web :).
Saludos