<?php
/**
 * PDBSKiP v0.0.0.1
 *
 * @description PDB parse && repack
 * @author secator
 * @link https://secator.com/
 * @date 24.10.2024
 */


if (empty($_SERVER['argv'][1])) {
    echo 
'usage: ' basename(__FILE__) . ' chrome.dll.pdb';
    exit;
}

if (!
is_file($_SERVER['argv'][1]) || !file_exists($_SERVER['argv'][1])) {
    echo 
'file not found';
    exit;
}

// MSF заголовок, для unpack и pack
// size => 0x38
$msf = array(
    
'signature' => 'a32',       // Microsoft C/C++ MSF 7.00 ...
    
'page_size' => 'V',         // количество байт в странице/блоке
    
'free_page' => 'V',         // не используется (значение 1 или 2)
    
'pages' => 'V',             // всего страниц в файле
    
'directory_size' => 'V',    // размер основной страницы
    
'reserved' => 'V',          // reserved
    
'directory' => 'V',         // на какой странице основная страница
);


$from fopen($_SERVER['argv'][1], 'r+');

$header unpack(implode('/'array_map(function ($key$value) { return $value $key; }, array_keys($msf),array_values($msf))), fread($from0x38));
if (
$header['signature'] != "Microsoft C/C++ MSF 7.00\r\n\x1ADS\0\0\0") {
    echo 
'Bad format';
    exit;
}

echo 
'Read ' $_SERVER['argv'][1] . PHP_EOL;

$original = array();

// количество основных страниц описания потоков
$directory_pages ceil($header['directory_size'] / $header['page_size']);

// читаем все номера основных страниц
fseek($from$header['directory'] * $header['page_size']);

$original['directory'] = array();
for (
$i 0$i $directory_pages$i++) {
    
$original['directory'][] = implode(unpack('V'fread($from4)));
}

// переход на описание всех потоков
$directory_current 0;
fseek($from$original['directory'][ $directory_current] * $header['page_size']);
$data fread($from$header['page_size']);

// количество всех потоков
$original['streams'] = implode(unpack('V'substr($data04)));
$data substr($data4);

// размер в байтах каждого потока
$original['stream_sizes'] = array();
for (
$i 0$i $original['streams']; $i++) {

    
$original['stream_sizes'][] = implode(unpack('V'substr($data04)));
    
$data substr($data,4);
    if (empty(
$data)) {
        
        
$directory_current++;
        
fseek($from$original['directory'][ $directory_current] * $header['page_size']);
        
$data fread($from$header['page_size']);
    }
}

// номера страниц каждого потока
$original['streams_pages'] = array();
for (
$i 0$i $original['streams']; $i++) {
    
// сколько страниц в текущем потоке
    
$pages ceil($original['stream_sizes'][ $i ] / $header['page_size']);
    if (
$pages 0) {
        
        
$list = array();
        for (
$j 0$j $pages$j++) {
            
$list[] = implode(unpack('V'substr($data04)));
            
$data   substr($data4);
            if (empty(
$data)) {
                
$directory_current++;
                
fseek($from$original['directory'][ $directory_current] * $header['page_size']);
                
$data fread($from$header['page_size']);
            }
        }
        
$original['streams_pages'][] = $list;
    } else {
        
$original['streams_pages'][] = array();
    }
}

// что будем пропускать

$skipping = array();
// пропускаем TPI (2), тогда нужны все потоки
$skipping[2] = array(
    
'name' => '.skip-tpi',
    
'streams' => array_keys($original['streams_pages'])
);
// пропускаем DBI (3), тогда хватит первых десяти
$skipping[3] = array(
    
'name' => '.skip-dbi',
    
'streams' => array(012345678910)
);


function 
align($to$size ) {
    if (
ftell($to) % $size) {
        for (
$i 0$ic $size - (ftell($to) % $size); $i $ic$i++) {
            
fwrite($tochr(0x00));
        }
    }
    return ;
}



foreach (
$skipping as $index => $skip) {
    
    
$filename substr_replace($_SERVER['argv'][1], $skip['name'], (int)strrpos($_SERVER['argv'][1], '.'), 0);
    echo 
'Pack ' $filename PHP_EOL;
    
    
$to fopen($filename'w+');
    
    
// первые блоки в нули, потом перезапишем
    
for ($i 0$i $header['page_size'] * ($header['free_page'] + 1); $i++) {
        
fwrite($tochr(0x00));
    }
    
    
//
    
$streams = array(
        
count($skip['streams']),
        
'',
    );
    
$bytes 8;
    
    
// 0 - описание всех потоков
    
$stream_number = array(=> array());
    
    for (
$i 1$ic count($skip['streams']); $i $ic$i++) {
        
$bytes += 4;
        
        
$pages $original['streams_pages'][ $i ];
        
        
$stream_number$i ] = array();
        
        if (
$i === $index) {
            
$streams[] = 0;
            continue;
        }
        
        
$streams[] = $original['stream_sizes'][ $i ];
        
        for (
$j 0$jc count($pages); $j $jc$j++) {
            
$bytes += 4;
            
            
$stream_number$i ][] = ftell($to) / $header['page_size'];
            
            
fseek($from$pages$j ] * $header['page_size']);
            
$data fread($from$header['page_size']);
            
fwrite($to$data);
        }
    }
    
    
// количество страниц в описании потоков
    
$pages ceil($bytes $header['page_size']);
    
    
$directory ftell($to) / $header['page_size'];
    for (
$i 0$i $pages$i++) {
        
$stream_number[0][] = $directory $i;
    }
    
    
// количество потоков
    
$streams[1] = $bytes;
    
    
// размеры потоков
    
foreach ($streams as $data) {
        
fwrite($topack('V'$data));
    }
    
    
// номера страниц
    
foreach ($stream_number as $size) {
        foreach (
$size as $data) {
            
fwrite($topack('V'$data));
        }
    }
    
    
// выравниваем страницу
    
align($to$header['page_size']);
    
    
// записываем основную страницу
    
$directory_pages ftell($to) / $header['page_size'];
    for (
$i 0$i $pages$i++) {
        
fwrite($topack('V'$directory $i));
    }
    
    
// выравниваем страницу
    
align($to$header['page_size']);
    
    
// переписываем заголовок
    
$header['pages']          = $directory_pages 1;
    
$header['directory']      = $directory_pages;
    
$header['directory_size'] = $pages $header['page_size'];
    
    
fseek($to0);
    
fwrite($topack(implode($msf), ...array_values($header)));
    
    
fclose($to);
}