[APORTE] [PowerShell] Modificar aleatoriamente el checksum de un archivo ejecutable
(1/1)
Eleкtro:
El siguiente script, desarrollado en PowerShell, sirve para modificar de forma aleatoria el checksum actual de un archivo ejecutable (formato PE).
Este procedimiento podría ser útil, por ejemplo, para evitar comprobaciones simples de integridad de archivos o verificaciones de firma que dependen de valores de checksum fijos, como los que pudieran estar implementados por sistemas anti-trampas en videojuegos y otro software de protección.
Para cumplir con este objetivo, el script cubre dos técnicas combinadas. La primera consiste en inyectar un valor aleatorio en el campo opcional de checksum del encabezado PE. Esta técnica es necesaria para alterar el resultado de la validación de la API de Windows 'MapFileAndCheckSum', ya que no calcula el checksum de forma corriente sobre todo el archivo, sino que utiliza el valor presente en ese campo del PE.
La segunda técnica consiste en agregar una cantidad pequeña de bytes de relleno o padding (entre 4 y 16 bytes) al final del archivo para alterar el tamaño del mismo, y por consiguiente el cálculo del checksum del archivo, sin afectar a la funcionalidad del ejecutable.
El mismo archivo "reabierto" tras la modificación:
⚠️ Importante: no utilizar el script con archivos PE que tengan un certificado digital, ya que el archivo se corromperá.
24 de septiembre de 2025: Script actualizado para identificar archivos PE que contienen una tabla de certificados y abortar la operación de inmediato, evitando así la posible corrupción del ejecutable.
Aunque considero haber probado el código lo suficiente, no me hago responsable de posibles daños causados al intentar modificar un archivo. Hagan siempre una copia de seguridad antes de modificar un archivo. 👍
Randomize executable checksum.ps1
El código fuente
Código
<#PSScriptInfo
.VERSION 1.1
.GUID A1B2C3D4-E5F6-7890-ABCD-EF1234567890
.AUTHOR ElektroStudios
.COMPANYNAME ElektroStudios
.COPYRIGHT ElektroStudios © 2025
#>
<#
===========================================================================================
| |
| User Fields |
| |
===========================================================================================
#>
# Path to the executable file to randomize its header and file checksums.
$exePath = ".\MyExecutable.exe"
<#
===========================================================================================
| |
| .NET Code |
| |
===========================================================================================
#>
Add-Type @"
using System;
using System.Runtime.InteropServices;
public static class NativeMethods {
[DllImport("imagehlp.dll", SetLastError = true)]
public static extern uint MapFileAndCheckSum(string filename, out uint headerSum, out uint checkSum);
}
"@
Add-Type -TypeDefinition @"
using System;
public class CRC32
{
private static readonly uint[] Table = new uint[256];
private uint crc;
static CRC32()
{
uint poly = 0xEDB88320;
for (uint i = 0; i < 256; i++)
{
uint temp = i;
for (int j = 0; j < 8; j++)
{
if ((temp & 1) == 1)
temp = (temp >> 1) ^ poly;
else
temp >>= 1;
}
Table[i] = temp;
}
}
public CRC32()
{
crc = 0xFFFFFFFF;
}
public void Update(byte[] buffer, int offset, int count)
{
for (int i = offset; i < offset + count; i++)
{
byte index = (byte)((crc ^ buffer[i]) & 0xFF);
crc = (crc >> 8) ^ Table[index];
}
}
public uint Compute(byte[] buffer)
{
Update(buffer, 0, buffer.Length);
return crc ^ 0xFFFFFFFF;
}
public static uint ComputeChecksum(byte[] buffer)
{
CRC32 crc32 = new CRC32();
return crc32.Compute(buffer);
}
}
"@
<#
===========================================================================================
| |
| Functions |
| |
===========================================================================================
#>
function Show-WelcomeScreen {
Clear-Host
Write-Host ""
Write-Host " $($host.ui.RawUI.WindowTitle)"
Write-Host " +================================================================+"
Write-Host " | |"
Write-Host " | This script modifies the file checksum of the specified |"
Write-Host " | executable file (PE format) by injecting a new random header |"
Write-Host " | checksum field and appending random padding bytes to alter the |"
Write-Host " | file checksum without affecting the executable's functionality.|"
Write-Host " | |"
Write-Host " | This process is useful to bypass simple file integrity checks |"
Write-Host " | or signature verifications that rely on fixed checksum values, |"
Write-Host " | such as those implemented by anti-cheat videogaming systems |"
Write-Host " | and similar software protection agents. |"
Write-Host " +================================================================+"
Write-Host ""
Write-Host " Script Settings " -ForegroundColor DarkGray
Write-Host " ================" -ForegroundColor DarkGray
Write-Host " Executable Path: $([System.IO.Path]::GetFullPath($exePath))" -ForegroundColor DarkGray
Write-Host ""
}
function Confirm-Continue {
Write-Host "Press 'Y' key to continue or 'N' to exit."
Write-Host ""
Write-Host "-Continue? (Y/N)"
do {
$key = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown")
$char = $key.Character.ToString().ToUpper()
if ($char -ne "Y" -and $char -ne "N") {
[console]::beep(1500, 500)
}
} while ($char -ne "Y" -and $char -ne "N")
if ($char -eq "N") {Exit(0)} else {Clear-Host}
}
function Read-UInt16($bytes, $offset) {
if ($offset -lt 0 -or $offset + 2 -gt $bytes.Length) {
throw "Offset $offset out of range for Read-UInt16"
}
return [BitConverter]::ToUInt16($bytes, $offset)
}
function Read-UInt32($bytes, $offset) {
if ($offset -lt 0 -or $offset + 4 -gt $bytes.Length) {
throw "Offset $offset out of range for Read-UInt32"
}
return [BitConverter]::ToUInt32($bytes, $offset)
}
function Write-UInt32([byte[]]$bytes, $offset, [uint32]$value) {
if ($offset -lt 0 -or $offset + 4 -gt $bytes.Length) {
throw "Offset $offset out of range for Write-UInt32"
}
$valBytes = [BitConverter]::GetBytes($value)
[Array]::Copy($valBytes, 0, $bytes, $offset, 4)
}
function Randomize-ExecutableChecksum{
param(
[string]$exePath
)
# Read the raw file bytes
if (-Not (Test-Path $exePath)) {
Show-GoodbyeScreen "File path '$([System.IO.Path]::GetFullPath($exePath))' does not exist." ([System.ConsoleColor]::Red)
}
[byte[]]$bytes = [System.IO.File]::ReadAllBytes($exePath)
# Locate e_lfanew (pointer to PE header)
$e_lfanew = Read-UInt32 $bytes 0x3C
# Check that e_lfanew is within valid range (at least 4 bytes from the end)
if ($e_lfanew -ge $bytes.Length - 4) {
Show-GoodbyeScreen "Invalid e_lfanew or corrupt file." ([System.ConsoleColor]::Red)
}
# Read and validate the PE signature (should be "PE\0\0") at the offset pointed by e_lfanew
$peSignature = [System.Text.Encoding]::ASCII.GetString($bytes, $e_lfanew, 4)
if ($peSignature -ne "PE`0`0") {
Show-GoodbyeScreen "Not a valid PE file (PE signature not found)." ([System.ConsoleColor]::Red)
}
# Calculate Optional Header offset (PE signature + File Header)
$optionalHeaderOffset = $e_lfanew + 4 + 20
# Read Magic field in the Optional Header that determines PE32/PE32+
$magic = Read-UInt16 $bytes $optionalHeaderOffset
if ($magic -eq 0x10b) {
# PE32 (32-bit)
$checksumOffset = $optionalHeaderOffset + 64
$dataDirectoryOffset = $optionalHeaderOffset + 96
} elseif ($magic -eq 0x20b) {
# PE32+ (64-bit)
$checksumOffset = $optionalHeaderOffset + 64
$dataDirectoryOffset = $optionalHeaderOffset + 112
} else {
Show-GoodbyeScreen ("Unknown or unsupported PE format. Magic = 0x{0:X}" -f $magic) ([System.ConsoleColor]::Red)
}
# Read Certificate Table directory (DataDirectory[4])
$certSize = Read-UInt32 $bytes ($dataDirectoryOffset + 4*8 + 4) # IMAGE_DATA_DIRECTORY::Size
if ($certSize -gt 0) {
Show-GoodbyeScreen "This PE file has a certificate table. Operation aborted to avoid breaking PE signature." ([System.ConsoleColor]::Red)
}
# Calculate the actual CRC32 file checksum
$currentCRC32 = '{0:x8}' -f ([CRC32]::ComputeChecksum($bytes))
# Calculate the actual header and file checksums using Windows API
[uint32]$currentHeaderSum = 0
[uint32]$currentFileSum = 0
[NativeMethods]::MapFileAndCheckSum($exePath, [ref]$currentHeaderSum, [ref]$currentFileSum) | Out-Null
# Generate a new random header checksum value
$randomBytes = New-Object byte[] 4
[System.Security.Cryptography.RandomNumberGenerator]::Create().GetBytes($randomBytes)
$newHeaderSum = [BitConverter]::ToUInt32($randomBytes, 0)
# Write the new random header checksum value into the file bytes
Write-UInt32 $bytes $checksumOffset $newHeaderSum
# Add useless padding bytes at the end (overlay) of the file bytes to generate a new file checksum
$paddingLength = Get-Random -Minimum 4 -Maximum 16
$padding = New-Object byte[] $paddingLength
[Security.Cryptography.RandomNumberGenerator]::Create().GetBytes($padding)
$newBytes = New-Object byte[] ($bytes.Length + $paddingLength)
[Array]::Copy($bytes, 0, $newBytes, 0, $bytes.Length)
[Array]::Copy($padding, 0, $newBytes, $bytes.Length, $paddingLength)
# Calculate the new CRC32 file checksum
$newCRC32 = '{0:x8}' -f ([CRC32]::ComputeChecksum($newBytes))
Write-Host "========== CHECKSUM INFORMATION ==========" -ForegroundColor Cyan
Write-Host "File: $exePath" -ForegroundColor Yellow
Write-Host ""
Write-Host "Header checksum field" -ForegroundColor Cyan
Write-Host "=====================" -ForegroundColor Gray
Write-Host "Current : $currentHeaderSum" -ForegroundColor White
Write-Host "Replacement: $newHeaderSum" -ForegroundColor White
Write-Host ""
Write-Host "Calculated CRC-32 file checksum" -ForegroundColor Cyan
Write-Host "===============================" -ForegroundColor Gray
Write-Host "Current : 0x$($currentCRC32.ToUpper())" -ForegroundColor White
Write-Host "Replacement: 0x$($newCRC32.ToUpper())" -ForegroundColor White
Write-Host ""
Write-Host "Calculated file checksum via 'MapFileAndCheckSum' API" -ForegroundColor Cyan
Write-Host "=====================================================" -ForegroundColor Gray
Write-Host "Current : $currentFileSum" -ForegroundColor White
Write-Host "Replacement: Can't be calculated until the" -ForegroundColor White
Write-Host " new header checksum field is" -ForegroundColor White
Write-Host " written to the actual file." -ForegroundColor White
Write-Host ""
Write-Host "-----------------------------------------------------" -ForegroundColor Gray
Write-Host ""
Confirm-Continue
try {
# Replace the source file by writing the changed bytes, containing the new header checksum and the padding bytes.
[System.IO.File]::WriteAllBytes($exePath, $newBytes)
} catch {
Show-GoodbyeScreen "Failed to write the modified bytes to the actual file. Please check file permissions and path." ([System.ConsoleColor]::Red)
}
# Calculate the new file checksum using Windows API
[uint32]$newFileSum = 0
[NativeMethods]::MapFileAndCheckSum($exePath, [ref]0, [ref]$newFileSum) | Out-Null
Write-Host "========== CHECKSUM INFORMATION ==========" -ForegroundColor Cyan
Write-Host "File: $exePath" -ForegroundColor Yellow
Write-Host ""
Write-Host "Header checksum field" -ForegroundColor Cyan
Write-Host "=====================" -ForegroundColor Gray
Write-Host "Previous: $currentHeaderSum" -ForegroundColor White
Write-Host "Current : $newHeaderSum" -ForegroundColor White
Write-Host ""
Write-Host "Calculated CRC-32 file checksum" -ForegroundColor Cyan
Write-Host "===============================" -ForegroundColor Gray
Write-Host "Previous: 0x$($currentCRC32.ToUpper())" -ForegroundColor White
Write-Host "Current : 0x$($newCRC32.ToUpper())" -ForegroundColor White
Write-Host ""
Write-Host "Calculated file checksum via 'MapFileAndCheckSum' API" -ForegroundColor Cyan
Write-Host "=====================================================" -ForegroundColor Gray
Write-Host "Previous: $currentFileSum" -ForegroundColor White
Write-Host "Current : $newFileSum" -ForegroundColor White
Write-Host ""
Write-Host "-----------------------------------------------------" -ForegroundColor Gray
Write-Host ""
}
function Show-GoodbyeScreen{
param(
[string]$msg,
[string]$forecolor = "White"
)
Write-Host $msg -BackgroundColor Black -ForegroundColor $forecolor
Write-Host ""
Write-Host "Press any key to exit..."
$key = $Host.UI.RawUI.ReadKey("NoEcho, IncludeKeyDown")
Exit(0)
}
<#
===========================================================================================
| |
| Main |
| |
===========================================================================================
#>
[System.Console]::Title = "Randomize executable checksum - by Elektro"
#[System.Console]::SetWindowSize(150, 45)
[CultureInfo]::CurrentUICulture = "en-US"
if (-not ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
Write-Host "Please run this script as Administrator!" -ForegroundColor Red
Pause
exit
}
try { Set-ExecutionPolicy -ExecutionPolicy "Unrestricted" -Scope "Process" } catch { }
Show-WelcomeScreen
Confirm-Continue
Randomize-ExecutableChecksum $exePath
Show-GoodbyeScreen "Operation Completed!" ([System.ConsoleColor]::Green)
Aspectos a tener en cuenta antes de usar
La configuración del script está definida directamente en el código y no admite parámetros por línea de comandos.
Citar
Código
<#
===========================================================================================
| |
| User Fields |
| |
===========================================================================================
#>
# Path to the executable file to randomize its header and file checksums.
$exePath = ".\MyExecutable.exe"
Atentamente,
Elektro. 👋
W17CH3R:
Gracias por el aporte, y también por el aviso de archivos PE que tengan un certificado digital puedan corromperse, lo testeare en una máquina virtual con Windows.
De nuevo, excelente aporte. :)
Eleкtro:
Cita de: W17CH3R en 23 Septiembre 2025, 20:01 pm
Gracias por el aporte, y también por el aviso de archivos PE que tengan un certificado digital puedan corromperse
Gracias a ti por el comentario.
He actualizado el script para identificar archivos PE que contienen una tabla de certificados y abortar la operación de inmediato, evitando así la posible corrupción del ejecutable.
El problema radica en que el certificado digital de un archivo PE se ubica (o debería ubicarse) al final del flujo de datos del archivo. En un PE firmado, la tabla de certificados tiene un tamaño fijo que se define dentro del propio PE, y la firma puede incluir un hash del archivo (llamémosle digest o 'image hash'): https://learn.microsoft.com/es-es/windows/win32/debug/pe-format#certificate-data - que se calcula tomando en cuenta las partes del PE excepto la zona ocupada por el certificado.
Por ese motivo, cualquier byte de relleno que se agregue al final del archivo se considerará parte de la zona excluida del certificado, y esto provoca que el digest (image hash) calculado del PE ya no coincida con el almacenado en la firma, lo que hace que Windows muestre un error genérico al intentar ejecutar el archivo: "No se puede ejecutar esta aplicación en el equipo."
Para hacerlo correctamente con archivos PE firmados habría que añadir una cantidad de padding al final de la tabla de certificados (al final del archivo) pero manteniendo una alineación de 8 bytes, es decir, añadir entre 1 a 8 bytes hasta alinear la tabla, y asignar un nuevo tamaño de la tabla de certificados dentro del PE para incluir este padding y que así los bytes de relleno se excluyan del cálculo del digest o image hash y Windows no lance un error al intentar ejecutar el archivo.
Eso me resultaría tedioso implementarlo con PowerShell. Sin embargo, y por si te sirve de algo a ti o alguna otra persona interesada, compartí un código en VB.NET que precisamente sirve para hacer todo esto, permitiendo adjuntar cualquier cosa (como bytes de relleno) al final de la tabla de certificado de un archivo PE.
El código es este: https://foro.elhacker.net/net_c_vbnet_asp/libreria_de_snippets_para_vbnet_compartan_aqui_sus_snippets-t378770.0.html;msg2285007#msg2285007
(el código completo está repartido en tres posts a partir de ese primer post.)
Con ese código en VB.NET se puede alterar el cálculo del file checksum de un archivo PE firmado digitalmente, sin corromperlo. Se usaría de la siguiente manera para añadir, por ejemplo, dos bytes de relleno a la tabla de certificados (sin contar los bytes de relleno que se añadirán automáticamente para mantener la tabla de certificados alineada):
Código
Dim inputFilePath As String = ".\executable.exe"
Dim outputFilePath As String = ".\executable_patched.exe"
Dim padding As Byte() = {&H0, &H0}
AppendBlobToPECertificateTable(inputFilePath, outputFilePath, padding)
Sin embargo, en algunos casos hay que seguir teniendo especial cuidado:
Cita de: Eleкtro en 21 Septiembre 2025, 12:36 pm
me he topado con software comercial de terceros que parecen hacer sus propias comprobaciones de integridad del archivo ejecutable, por lo que al modificar el PE, dan un error genérico al intentar iniciar el programa. En conclusión, hay que verificar que el archivo ejecutable modificado funcione correctamente, sin asumir nada.
Un saludo.
W17CH3R:
Disculpa la tardanza, en Septiembre me matrícule en ASIR, y estado ocupado con trabajos de clase y exámenes.
Cita de: Eleкtro en 24 Septiembre 2025, 06:53 am
Sin embargo, en algunos casos hay que seguir teniendo especial cuidado:
Totalmente, de hecho lo testeado en un entorno controlado, nunca testeo en mi host principal para evitar posibles daños, sin embargo excelente trabajo :)
Navegación