Tool: Check CBS Corruption

I hacked together a small tool in .NET that helps me quickly analyze the Component Based Servicing (CBS) log in c:\windows\logs\cbs\cbs.log for missing CSI Payloads.

It will parse the log file selected and extract the packages. A few options are available to then search for the missing payloads by specifying a source directory (like a known good copy of \windows\winsxs folder.

This GUI is a culmination of a few PowerShell scripts I hacked together to basically do the same thing. The only thing I do not have in the GUI is the ability to convert the UBR to a KB, so for example if a missing package for Windows Server 2019 is amd64_microsoft-windows-f..rcluster-clientcore_31bf3856ad364e35_10.0.17763.3469_none_decef48d0a3310cc, the UBR is 3469 and that is found in KB5017379 which can be determined by visiting Microsoft’s Windows Server release information.

I did write a PowerShell script that retrieves the Windows Server release information, if anyone is interested. It takes an argument (win10, win11, server2019, server2022) and defaults to server2019 if no argument provided.

The PowerShell version of this is available in my GitHub repo.

Check CBS Corruption

This is the main interface. After selecting the CBS log file, it will parse it and display any lines with CSI Payload Missing. It writes a .fix file and displays the contents on the right pane. These are the missing folders.

From there, a few options I needed was to copy to clipboard, and also generate copy command. The generate copy command just utilizes robocopy and hardcodes a fake destination that will need changed. Alternatively, you can use the Search for Folders button and choose a source folder to search. By default it will start in c:\windows\winsxs. If you want to use recursion, be sure to check the recursive checkbox.

This is the results of the Search for Folders button. If it finds files, the left pane lists the location where the folders are. You can then use the Copy Found to destination button which lets you choose a destination folder and will then copy the found folders entirely to the destination. Alternatively a couple options exist to Copy to Clipboard and Generate Copy Command (which generates a robocopy command like previous).

Recover media files from iMazing backup

Awhile back I used a piece of software (iMazing) to backup an old iPhone of mine. The raw backup when extracted is a bunch of files without extensions in many subdirs. I needed a way to extract actual images from the backup folder and copy them to an extracted folder giving them the correct file extension and ensuring I kept the date created / modified in tact.

Here’s a PowerShell script that will recursively go through a specified folder and copy any files that are images or videos to an extracted folder location.

# Define the magic numbers for various image and video types
$fileTypes = @{
jpg = @(0xFF, 0xD8, 0xFF)
png = @(0x89, 0x50, 0x4E, 0x47)
gif = @(0x47, 0x49, 0x46, 0x38)
bmp = @(0x42, 0x4D)
mp4 = @(0x00, 0x00, 0x00, 0x18, 0x66, 0x74, 0x79, 0x70, 0x69, 0x73, 0x6F, 0x6D)
avi = @(0x52, 0x49, 0x46, 0x46, 0x00, 0x00, 0x00, 0x00, 0x41, 0x56, 0x49, 0x20)
mkv = @(0x1A, 0x45, 0xDF, 0xA3)
mov = @(0x00, 0x00, 0x00, 0x14, 0x66, 0x74, 0x79, 0x70, 0x71, 0x74, 0x20, 0x20)
wmv = @(0x30, 0x26, 0xB2, 0x75, 0x8E, 0x66, 0xCF, 0x11)
}

# Function to get the file header as a byte array
function Get-FileHeader {
param (
[string]$filePath,
[int]$headerLength = 12
)

$fileBytes = [System.IO.File]::ReadAllBytes($filePath)
return $fileBytes[0..($headerLength - 1)]
}

# Function to match the file header with known file types
function Get-FileType {
param (
[byte[]]$fileHeader
)

foreach ($fileType in $fileTypes.Keys) {
$magicNumbers = $fileTypes[$fileType]
$match = $true

for ($i = 0; $i -lt $magicNumbers.Length; $i++) {
if ($fileHeader[$i] -ne $magicNumbers[$i] -and $magicNumbers[$i] -ne 0x00) {
$match = $false
break
}
}

if ($match) {
return $fileType
}
}

return $null
}

# Source and destination directories
$sourceDirectory = "C:\iPhoneBackup"
$destinationDirectory = "D:\Recovery"

# Ensure the destination directory exists
if (-not (Test-Path -Path $destinationDirectory)) {
New-Item -ItemType Directory -Path $destinationDirectory | Out-Null
}

# Get all files in the source directory recursively
$files = Get-ChildItem -Path $sourceDirectory -Recurse -File

foreach ($file in $files) {
$header = Get-FileHeader -filePath $file.FullName
$fileType = Get-FileType -fileHeader $header

if ($fileType) {
# Create the relative path for the destination file
$relativePath = $file.FullName.Substring($sourceDirectory.Length)
$newFileName = [System.IO.Path]::ChangeExtension($relativePath, ".$fileType")
$destinationPath = Join-Path -Path $destinationDirectory -ChildPath $newFileName

# Ensure the destination subdirectory exists
$destinationSubDir = [System.IO.Path]::GetDirectoryName($destinationPath)
if (-not (Test-Path -Path $destinationSubDir)) {
New-Item -ItemType Directory -Path $destinationSubDir | Out-Null
}

# Copy the file to the destination with the new extension
Copy-Item -Path $file.FullName -Destination $destinationPath

# Preserve the original file timestamps
$originalCreationTime = Get-ItemProperty -Path $file.FullName -Name CreationTime
$originalLastWriteTime = Get-ItemProperty -Path $file.FullName -Name LastWriteTime
$originalLastAccessTime = Get-ItemProperty -Path $file.FullName -Name LastAccessTime

Set-ItemProperty -Path $destinationPath -Name CreationTime -Value $originalCreationTime.CreationTime
Set-ItemProperty -Path $destinationPath -Name LastWriteTime -Value $originalLastWriteTime.LastWriteTime
Set-ItemProperty -Path $destinationPath -Name LastAccessTime -Value $originalLastAccessTime.LastAccessTime

Write-Output "Copied $($file.FullName) to $destinationPath"
} else {
Write-Output "$($file.FullName) is not a recognized image or video file"
}
}

Cacti Dell R750 iDRAC Temperature

I’ve setup a template to monitor a Dell R750 server via configured SNMP access on iDRAC.

My server has only two CPU (Intel Xeon Gold 6338N). I did not create this as a robust type of template, where it will walk all CPUs. Eventually I’ll get to that, but for now each data source OID is set manually.

Here are the OIDs:

OIDDescription
.1.3.6.1.4.1.674.10892.5.4.700.20.1.6.1.1CPU1 Temp
.1.3.6.1.4.1.674.10892.5.4.700.20.1.6.1.2CPU2 Temp
.1.3.6.1.4.1.674.10892.5.4.700.20.1.6.1.3System Board Inlet Temp
.1.3.6.1.4.1.674.10892.5.4.700.20.1.6.1.4System Board Exhaust Temp

Download

A Port Scanner in C

The following port scanner C code checks approximately 65535 ports in about 15 seconds on-network.

#include <stdio.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <sys/wait.h>

#define MAX_PORTS 65535
#define MAX_SOCKETS 1023

// gcc -o portscanner portscanner.c

int scan_port(const char* host, int port) {
    struct sockaddr_in server;
    int sock;

    sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock == -1) {
        perror("socket");
        return -1;
    }

    server.sin_addr.s_addr = inet_addr(host);
    server.sin_family = AF_INET;
    server.sin_port = htons(port);

    int flags = fcntl(sock, F_GETFL, 0);
    fcntl(sock, F_SETFL, flags | O_NONBLOCK);

    if (connect(sock, (struct sockaddr *)&server, sizeof(server)) == -1) {
        if (errno != EINPROGRESS) {
            perror("connect");
            return -1;
        }
    }

    fd_set fdset;
    struct timeval tv;

    FD_ZERO(&fdset);
    FD_SET(sock, &fdset);

    tv.tv_sec = 0;
    tv.tv_usec = 200000;  // Timeout of 200ms

    int select_ret = select(sock + 1, NULL, &fdset, NULL, &tv);

    if (select_ret == -1) {
        perror("select");
        return -1;
    }
    else if (select_ret == 0) {
        close(sock);
        return 0;
    }

    // Connection successful, port is open
    close(sock);
    return 1;
}

void scan_ports(const char* host, int start_port, int end_port) {
    int num_sockets = 0;
    int port;

    for (port = start_port; port <= end_port; port++) {
        pid_t pid = fork();
        if (pid == -1) {
            perror("fork");
            exit(EXIT_FAILURE);
        } else if (pid == 0) {
            // Child process
            int result = scan_port(host, port);
            if (result == 1) {
                printf("Port %d is open\n", port);
            }
            exit(EXIT_SUCCESS);
        } else {
            // Parent process
            num_sockets++;
            if (num_sockets >= MAX_SOCKETS) {
                wait(NULL);
                num_sockets--;
            }
        }
    }
}

int main(int argc, char* argv[]) {
    if (argc < 3 || argc > 4) {
        printf("Usage: %s <host> <port/start_port> [end_port]\n", argv[0]);
        return 1;
    }

    const char* host = argv[1];
    int start_port = atoi(argv[2]);
    int end_port;

    if (argc == 4) {
        end_port = atoi(argv[3]);
        if (start_port < 1 || start_port > MAX_PORTS || end_port < 1 || end_port > MAX_PORTS || start_port > end_port) {
            printf("Invalid port range.\n");
            return 1;
        }
    } else {
        end_port = start_port;
        if (start_port < 1 || start_port > MAX_PORTS) {
            printf("Invalid port.\n");
            return 1;
        }
    }

    printf("Scanning ports %d to %d on host %s...\n", start_port, end_port, host);
    scan_ports(host, start_port, end_port);
    return 0;
}

Here is a Windows version; it is much slower.

// CFLAGS="-static-libgcc -static-libstdc++ -03"
// i686-w64-mingw32-gcc -o portscanner.exe portscanner_win32.c -lws2_32

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <process.h>

#pragma comment(lib, "ws2_32.lib")

#define MAX_PORTS 65535

typedef struct {
    const char* host;
    int start_port;
    int end_port;
} ScanParams;

int scan_port(const char* host, int port) {
    WSADATA wsaData;
    SOCKET sock;
    struct sockaddr_in server;

    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
        perror("WSAStartup");
        return -1;
    }

    sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock == INVALID_SOCKET) {
        perror("socket");
        WSACleanup();
        return -1;
    }

    server.sin_addr.s_addr = inet_addr(host);
    server.sin_family = AF_INET;
    server.sin_port = htons(port);

    u_long nonblocking = 1;
    if (ioctlsocket(sock, FIONBIO, &nonblocking) != 0) {
        perror("ioctlsocket");
        closesocket(sock);
        WSACleanup();
        return -1;
    }

    if (connect(sock, (struct sockaddr*)&server, sizeof(server)) == SOCKET_ERROR) {
        int error = WSAGetLastError();
        if (error != WSAEWOULDBLOCK) {
            perror("connect");
            closesocket(sock);
            WSACleanup();
            return -1;
        }
    }

    fd_set fdset;
    struct timeval tv;

    FD_ZERO(&fdset);
    FD_SET(sock, &fdset);

    tv.tv_sec = 0;
    tv.tv_usec = 200000;  // Timeout of 200ms

    int select_ret = select(0, NULL, &fdset, NULL, &tv);

    if (select_ret == SOCKET_ERROR) {
        perror("select");
        closesocket(sock);
        WSACleanup();
        return -1;
    } else if (select_ret == 0) {
        closesocket(sock);
        WSACleanup();
        return 0;
    }

    // Connection successful, port is open
    closesocket(sock);
    WSACleanup();
    return 1;
}

unsigned int __stdcall scan_ports_thread(void* params) {
    ScanParams* scanParams = (ScanParams*)params;
    const char* host = scanParams->host;
    int start_port = scanParams->start_port;
    int end_port = scanParams->end_port;
    int port;

    for (port = start_port; port <= end_port; port++) {
        int result = scan_port(host, port);
        if (result == 1) {
            printf("Port %d is open\n", port);
        }
    }

    _endthreadex(0);
    return 0;
}

void scan_ports(const char* host, int start_port, int end_port, int num_threads) {
    int ports_per_thread = (end_port - start_port + 1) / num_threads;
    int remaining_ports = (end_port - start_port + 1) % num_threads;
    int thread;
    HANDLE* scanThreads = malloc(num_threads * sizeof(HANDLE));
    ScanParams* scanParams = malloc(num_threads * sizeof(ScanParams));

    for (thread = 0; thread < num_threads; thread++) {
        scanParams[thread].host = host;
        scanParams[thread].start_port = start_port + thread * ports_per_thread;
        scanParams[thread].end_port = scanParams[thread].start_port + ports_per_thread - 1;

        if (thread == num_threads - 1) {
            // Assign remaining ports to the last thread
            scanParams[thread].end_port += remaining_ports;
        }

        scanThreads[thread] = (HANDLE)_beginthreadex(NULL, 0, scan_ports_thread, &scanParams[thread], 0, NULL);
    }

    // Wait for all threads to finish
    WaitForMultipleObjects(num_threads, scanThreads, TRUE, INFINITE);

    free(scanThreads);
    free(scanParams);
}

int main(int argc, char* argv[]) {
    if (argc < 3 || argc > 5) {
        printf("Usage: %s <host> <port/start_port> [end_port] [num_threads]\n", argv[0]);
        return 1;
    }

    const char* host = argv[1];
    int start_port = atoi(argv[2]);
    int end_port;
    int num_threads = 25;

    if (argc >= 4) {
        end_port = atoi(argv[3]);
        if (argc == 5) {
            num_threads = atoi(argv[4]);
        }
    } else {
        end_port = start_port;
    }

    if (start_port < 1 || start_port > MAX_PORTS || end_port < 1 || end_port > MAX_PORTS || start_port > end_port) {
        printf("Invalid port range.\n");
        return 1;
    }

    printf("Scanning ports %d to %d on host %s using %d thread(s)...\n", start_port, end_port, host, num_threads);
    scan_ports(host, start_port, end_port, num_threads);
    return 0;
}