├── .gitignore ├── README.md └── regdump.c /.gitignore: -------------------------------------------------------------------------------- 1 | *.o* 2 | *.exe 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Registry Dump 2 | 3 | Dump one or more registry hives as text, one line per value. Normally values 4 | and empty keys are written; use `-v` to only show values, or `-k` to only 5 | show keys (along with the time of last write). 6 | 7 | Key names, value names and strings will only use ASCII characters, other 8 | characters will be written as `` or ``, using the hexadecimal code 9 | of the character. 10 | 11 | String types will stop at the first null (or double null, for multi), adding 12 | `<...>` to indicate if there is more non-null data; use `-s` to display 13 | everything (although trailing nulls are still not shown). Multi-strings will 14 | be separated by `<>`. 15 | 16 | If binary data is predominantly ASCII (7 out of 8 bytes, or 3 out of 4 words) 17 | it will be displayed as a string, always showing everything (including trailing 18 | nulls). If 8-byte data matches a 21st century `FILETIME` it will be shown as 19 | date and time (local), as well as data. 20 | 21 | Some non-standard value types are supported. Types under the `Properties` 22 | key having the high 16 bits set will be treated as a device property type 23 | (`0xFFFF0000 | DEVPROP_TYPE...`) and translated to a corresponding standard 24 | type. Types under the `DriverPackages` key will mask out the high word, 25 | resulting in a standard type. 26 | 27 | Note: assumes the hive and CPU are little-endian. 28 | 29 | References: 30 | 31 | https://www.codeproject.com/KB/recipes/RegistryDumper.aspx 32 | https://github.com/msuhanov/regf/blob/master/Windows%20registry%20file%20format%20specification.md 33 | -------------------------------------------------------------------------------- /regdump.c: -------------------------------------------------------------------------------- 1 | /* 2 | regdump.c - Dump a registry hive. 3 | 4 | Jason Hood, 29 January to 9 February, 2019. 5 | Based on code by Ladislav Nevery, 2008. 6 | 7 | Dump one or more registry hives as text, one line per value. Normally values 8 | and empty keys are written; use "-v" to only show values, or "-k" to only 9 | show keys (along with the time of last write). 10 | 11 | Key names, value names and strings will only use ASCII characters, other 12 | characters will be written as "" or "", using the hexadecimal code 13 | of the character. 14 | 15 | String types will stop at the first null (or double null, for multi), adding 16 | "<...>" to indicate if there is more non-null data; use "-s" to display 17 | everything (although trailing nulls are still not shown). Multi-strings will 18 | be separated by "<>". 19 | 20 | If binary data is predominantly ASCII (7 out of 8 bytes, or 3 out of 4 words) 21 | it will be displayed as a string, always showing everything (including 22 | trailing nulls). If 8-byte data matches a 21st century FILETIME it will be 23 | shown as date and time (local), as well as data. 24 | 25 | Some non-standard value types are supported. Types under the "Properties" 26 | key having the high 16 bits set will be treated as a device property type 27 | (0xFFFF0000 | DEVPROP_TYPE...) and translated to a corresponding standard 28 | type. Types under the "DriverPackages" key will mask out the high word, 29 | resulting in a standard type. 30 | 31 | Note: assumes the hive and CPU are little-endian. 32 | 33 | References: 34 | 35 | https://www.codeproject.com/KB/recipes/RegistryDumper.aspx 36 | https://github.com/msuhanov/regf/blob/master/Windows%20registry%20file%20format%20specification.md 37 | */ 38 | 39 | #ifdef _WIN32 40 | # define _CRT_SECURE_NO_WARNINGS 41 | # define WIN32_LEAN_AND_MEAN 42 | # include 43 | # define int64_t __int64 44 | # define PRId64 "I64d" 45 | # define PRIX64 "I64X" 46 | #else 47 | # include 48 | # include 49 | typedef int BOOL; 50 | # define TRUE 1 51 | # define FALSE 0 52 | #endif 53 | #include 54 | #include 55 | #include 56 | 57 | 58 | BOOL hex_type, only_values, only_keys, all_string, time_sec, time_full; 59 | BOOL big_data; 60 | 61 | 62 | typedef struct 63 | { 64 | char signature[4]; // "regf" 65 | int primary_sequence_number; 66 | int secondary_sequence_number; 67 | int last_written_timestamp[2]; // avoid alignment issues with int64_t 68 | int major_version; 69 | int minor_version; 70 | int file_type; 71 | int file_format; 72 | int root_cell_offset; 73 | // and more of no interest 74 | } base_block; 75 | 76 | 77 | typedef struct 78 | { 79 | int block_size; 80 | int offsets[1]; 81 | } offsets; 82 | 83 | 84 | typedef struct 85 | { 86 | int block_size; 87 | char block_type[2]; // "lf" "lh" "li" "ri" "db" 88 | short count; 89 | int offsets[1]; 90 | int hash; // only for "lf" "lh", ignored 91 | } list_block; 92 | 93 | 94 | typedef struct 95 | { 96 | int block_size; 97 | char block_type[2]; // "nk" 98 | short flags; 99 | int64_t timestamp; 100 | char dummya[8]; 101 | int subkey_count; 102 | char dummyb[4]; 103 | int subkeys; 104 | char dummyc[4]; 105 | int value_count; 106 | int values; 107 | char dummyd[28]; 108 | short len; 109 | short du; 110 | char name[1]; 111 | } key_block; 112 | 113 | 114 | typedef struct 115 | { 116 | int block_size; 117 | char block_type[2]; // "vk" 118 | short name_len; 119 | int size; 120 | int offset; 121 | int value_type; 122 | short flags; 123 | short dummy; 124 | char name[1]; 125 | } value_block; 126 | 127 | 128 | #define KEY_COMP_NAME 0x20 129 | #define VALUE_COMP_NAME 0x01 130 | 131 | 132 | #ifndef _WIN32 133 | enum 134 | { 135 | REG_NONE, 136 | REG_SZ, 137 | REG_EXPAND_SZ, 138 | REG_BINARY, 139 | REG_DWORD, 140 | REG_DWORD_BIG_ENDIAN, 141 | REG_LINK, 142 | REG_MULTI_SZ, 143 | REG_RESOURCE_LIST, 144 | REG_FULL_RESOURCE_DESCRIPTOR, 145 | REG_RESOURCE_REQUIREMENTS_LIST, 146 | REG_QWORD 147 | }; 148 | #endif 149 | 150 | 151 | enum 152 | { 153 | DEVPROP_TYPE_INT16 = 4, 154 | DEVPROP_TYPE_UINT16, 155 | DEVPROP_TYPE_INT32, 156 | DEVPROP_TYPE_UINT32, 157 | DEVPROP_TYPE_INT64, 158 | DEVPROP_TYPE_UINT64, 159 | DEVPROP_TYPE_FILETIME = 0x10, 160 | DEVPROP_TYPE_BOOLEAN, 161 | DEVPROP_TYPE_STRING, 162 | DEVPROP_TYPE_STRING_LIST = 0x2000 | DEVPROP_TYPE_STRING, 163 | DEVPROP_TYPE_STRING_INDIRECT = 0x19 164 | }; 165 | 166 | 167 | char* make_name( char* out, char* in, int len, int comp ) 168 | { 169 | int i; 170 | 171 | if (comp) 172 | { 173 | unsigned char* uc = (unsigned char*)in; 174 | for (i = 0; i < len; ++uc, ++i) 175 | { 176 | if (*uc >= 32 && *uc < 127) 177 | *out++ = *uc; 178 | else 179 | out += sprintf( out, "<%02X>", *uc ); 180 | } 181 | } 182 | else 183 | { 184 | unsigned short* us = (unsigned short*)in; 185 | for (i = 0; i < len / 2; ++us, ++i) 186 | { 187 | if (*us >= 32 && *us < 127) 188 | *out++ = (char)*us; 189 | else 190 | out += sprintf( out, "<%0*X>", (*us < 0x100) ? 2 : 4, *us ); 191 | } 192 | } 193 | *out = '\0'; 194 | return out; 195 | } 196 | 197 | 198 | void print_time( int64_t t, BOOL full, BOOL brackets ) 199 | { 200 | #ifdef _WIN32 201 | SYSTEMTIME st; 202 | #else 203 | time_t secs; 204 | struct tm* lt; 205 | #endif 206 | 207 | if (brackets) 208 | putchar( '[' ); 209 | 210 | #ifdef _WIN32 211 | FileTimeToSystemTime( (FILETIME*)&t, &st ); 212 | SystemTimeToTzSpecificLocalTime( NULL, &st, &st ); 213 | printf( "%u-%02u-%02u %02u:%02u:%02u", 214 | st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond ); 215 | #else 216 | // Translate 100-nanosecond intervals from 1601 to seconds from 1970. 217 | secs = (time_t)(t / 10000000) - 11644473600; 218 | lt = localtime( &secs ); 219 | printf( "%d-%02d-%02d %02d:%02d:%02d", 220 | lt->tm_year+1900, lt->tm_mon+1, lt->tm_mday, 221 | lt->tm_hour, lt->tm_min, lt->tm_sec ); 222 | #endif 223 | if (full) 224 | printf( ".%07d", (int)(t % 10000000) ); 225 | 226 | if (brackets) 227 | { 228 | putchar( ']' ); 229 | putchar( ' ' ); 230 | } 231 | } 232 | 233 | 234 | static char *root, *full; 235 | 236 | void walk( char* path, key_block* key ) 237 | { 238 | static BOOL properties, driverpackages; 239 | offsets* val_list; 240 | int size, type; 241 | char* data; 242 | char* data_block = NULL; 243 | BOOL* leave_key = NULL; 244 | BOOL empty_key; 245 | int bintext; 246 | int o, i; 247 | 248 | // Add current key name to printed path. 249 | *path++ = '/'; 250 | path = make_name( path, key->name, key->len, key->flags & KEY_COMP_NAME ); 251 | 252 | if (only_keys) 253 | { 254 | print_time( key->timestamp, time_full, TRUE ); 255 | printf( "%s\n", full ); 256 | empty_key = FALSE; 257 | goto children; 258 | } 259 | 260 | if (!properties) 261 | { 262 | if (key->len == 10 && memcmp( "Properties", key->name, key->len ) == 0) 263 | { 264 | properties = TRUE; 265 | leave_key = &properties; 266 | } 267 | } 268 | if (!driverpackages) 269 | { 270 | if (key->len == 14 && memcmp( "DriverPackages", key->name, key->len ) == 0) 271 | { 272 | driverpackages = TRUE; 273 | leave_key = &driverpackages; 274 | } 275 | } 276 | 277 | empty_key = (key->value_count == 0); 278 | 279 | // Print all contained values. 280 | val_list = (offsets*)(key->values + root); 281 | for (o = 0; o < key->value_count; ++o) 282 | { 283 | value_block* val = (value_block*)(val_list->offsets[o] + root); 284 | 285 | *path = '/'; 286 | if (val->name_len == 0) 287 | { 288 | path[1] = '@'; 289 | path[2] = '\0'; 290 | } 291 | else 292 | { 293 | make_name( path+1, val->name, val->name_len, val->flags & VALUE_COMP_NAME ); 294 | } 295 | 296 | if (time_sec || time_full) 297 | print_time( key->timestamp, time_full, TRUE ); 298 | 299 | size = val->size & 0x7fffffff; 300 | if (hex_type) 301 | printf( "[%08X:%08X] %s = ", val->value_type, size, full ); 302 | else 303 | printf( "%s [%d:%d] = ", full, val->value_type, size ); 304 | 305 | // Data are usually in separate blocks without types, but for small values 306 | // MS added optimization where if bit 31 is set data are contained within 307 | // the key itself to save space. 308 | if (val->size & (1 << 31)) 309 | data = (char*)&val->offset; 310 | else 311 | { 312 | data = val->offset + root + 4; 313 | if (size > 16344 && big_data && *data == 'd' && data[1] == 'b') 314 | { 315 | list_block* item; 316 | offsets* datalist; 317 | int left; 318 | 319 | item = (list_block*)(data - 4); 320 | datalist = (offsets*)(item->offsets[0] + root); 321 | left = size; 322 | data = data_block = malloc( size ); 323 | for (i = 0; i < item->count; ++i) 324 | { 325 | memcpy( data, datalist->offsets[i] + root + 4, 326 | (left > 16344) ? 16344 : left ); 327 | data += 16344; 328 | left -= 16344; 329 | } 330 | data = data_block; 331 | } 332 | } 333 | 334 | type = val->value_type; 335 | if (properties && (type & 0xFFFF0000) == 0xFFFF0000) 336 | { 337 | switch (type & 0xFFFF) 338 | { 339 | case DEVPROP_TYPE_INT32: 340 | case DEVPROP_TYPE_UINT32: 341 | type = REG_DWORD; 342 | break; 343 | case DEVPROP_TYPE_INT64: 344 | case DEVPROP_TYPE_UINT64: 345 | case DEVPROP_TYPE_FILETIME: 346 | type = REG_QWORD; 347 | break; 348 | case DEVPROP_TYPE_STRING: 349 | case DEVPROP_TYPE_STRING_INDIRECT: 350 | type = REG_SZ; 351 | break; 352 | case DEVPROP_TYPE_STRING_LIST: 353 | type = REG_MULTI_SZ; 354 | break; 355 | } 356 | } 357 | else if (driverpackages) 358 | type &= 0xFFFF; 359 | 360 | // See if binary data is text. 361 | bintext = 0; 362 | if ((type == REG_BINARY || type == REG_NONE) && size >= 8) 363 | { 364 | int ascii = 0, min = 8; 365 | if (data[1] == 0 && data[3] == 0) 366 | { 367 | unsigned short* us = (unsigned short*)data; 368 | if (*us >= 32 && *us < 127 && 369 | us[1] >= 32 && us[1] < 127) 370 | { 371 | ascii = 2; 372 | for (i = 2; i < size / 2; ++i) 373 | if (us[i] >= 32 && us[i] < 127) 374 | ++ascii; 375 | ascii *= 2; 376 | min = 6; 377 | } 378 | } 379 | else if (*data >= 32 && *data < 127 && 380 | data[1] >= 32 && data[1] < 127) 381 | { 382 | ascii = 2; 383 | for (i = 2; i < size; ++i) 384 | if (data[i] >= 32 && data[i] < 127) 385 | ++ascii; 386 | min = 7; 387 | } 388 | if (ascii * 8 >= size * min) 389 | bintext = (data[1] == 0) ? 16 : 8; 390 | } 391 | 392 | if (type == REG_DWORD && size == 4) 393 | { 394 | printf( "0x%X (%d)", *(int*)data, *(int*)data ); 395 | } 396 | else if (properties && size == 1 && 397 | (type == (0xFFFF0000 | DEVPROP_TYPE_BOOLEAN))) 398 | { 399 | if (*data == -1) 400 | printf( "true" ); 401 | else if (*data == 0) 402 | printf( "false" ); 403 | else 404 | printf( "%02X", *(unsigned char*)data ); 405 | } 406 | else if (properties && size == 2 && 407 | (type == (0xFFFF0000 | DEVPROP_TYPE_UINT16) || 408 | type == (0xFFFF0000 | DEVPROP_TYPE_INT16))) 409 | { 410 | if ((type & 0xFFFF) == DEVPROP_TYPE_UINT16) 411 | printf( "0x%X (%u)", *(unsigned short*)data, *(unsigned short*)data ); 412 | else 413 | printf( "0x%X (%d)", *(unsigned short*)data, *(short*)data ); 414 | } 415 | // See if 8 bytes is a 21st century FILETIME. 416 | else if (size == 8 && 417 | (type == REG_QWORD || type == REG_BINARY || type == REG_NONE) && 418 | *(int64_t*)data >= (int64_t)126227808000000000 && // 2001-01-01 419 | *(int64_t*)data < (int64_t)157784544000000000) // 2101-01-01 420 | { 421 | print_time( *(int64_t*)data, FALSE, FALSE ); 422 | if (type == REG_QWORD) 423 | printf( " (0x%" PRIX64 "; %" PRId64 ")", *(int64_t*)data, *(int64_t*)data ); 424 | else 425 | { 426 | putchar( ' ' ); 427 | putchar( '(' ); 428 | for (i = 0; i < size; i++) 429 | { 430 | if (i) 431 | putchar( ',' ); 432 | printf( "%02X", (unsigned char)data[i] ); 433 | } 434 | putchar( ')' ); 435 | } 436 | } 437 | else if (type == REG_QWORD && size == 8) 438 | { 439 | printf( "0x%" PRIX64 " (%" PRId64 ")", *(int64_t*)data, *(int64_t*)data ); 440 | } 441 | // Strings are stored as Unicode (UTF-16LE). 442 | else if (type == REG_SZ || 443 | type == REG_MULTI_SZ || 444 | type == REG_EXPAND_SZ || 445 | type == REG_LINK || 446 | bintext == 16) 447 | { 448 | unsigned short* us = (unsigned short*)data; 449 | size /= 2; 450 | if (!bintext) 451 | while (size > 0 && us[size-1] == '\0') 452 | --size; 453 | for (i = 0; i < size; ++i) 454 | { 455 | if (us[i] >= 32 && us[i] < 127) 456 | putchar( us[i] ); 457 | else if (us[i] == '\0' && type == REG_MULTI_SZ && i+1 < size && us[i+1] != '\0') 458 | printf( "<>" ); 459 | else if (us[i] == '\0' && !all_string && !bintext) 460 | { 461 | printf( " <...>" ); 462 | break; 463 | } 464 | else 465 | printf( "<%0*X>", (us[i] < 0x100) ? 2 : 4, us[i] ); 466 | } 467 | } 468 | else if (bintext /*== 8*/) 469 | { 470 | for (i = 0; i < size; ++i) 471 | { 472 | if (data[i] >= 32 && data[i] < 127) 473 | putchar( data[i] ); 474 | else 475 | printf( "<%02X>", (unsigned char)data[i] ); 476 | } 477 | } 478 | else 479 | { 480 | for (i = 0; i < size; ++i) 481 | { 482 | if (i) 483 | putchar( ',' ); 484 | printf( "%02X", (unsigned char)data[i] ); 485 | } 486 | } 487 | putchar( '\n' ); 488 | 489 | if (data_block) 490 | { 491 | free( data_block ); 492 | data_block = NULL; 493 | } 494 | } 495 | 496 | children: 497 | // For simplicity we can imagine keys as directories in filesystem and values 498 | // as files. Since we already dumped values for this dir we will now iterate 499 | // through subdirectories in the same way. 500 | if (key->subkeys != -1) 501 | { 502 | list_block* item = (list_block*)(key->subkeys + root); 503 | 504 | if (item->count) 505 | empty_key = FALSE; 506 | 507 | if (item->block_type[0] == 'l') 508 | { 509 | int ii = (item->block_type[1] == 'i') ? 1 : 2; 510 | for (i = 0; i < item->count; ++i) 511 | walk( path, (key_block*)(item->offsets[i*ii] + root) ); 512 | } 513 | else 514 | { 515 | for (i = 0; i < item->count; ++i) 516 | { 517 | // In case of too many subkeys this list contains just other lists. 518 | list_block* subitem = (list_block*)(item->offsets[i] + root); 519 | int j, jj = (subitem->block_type[1] == 'i') ? 1 : 2; 520 | for (j = 0; j < subitem->count; ++j) 521 | walk( path, (key_block*)(subitem->offsets[j*jj] + root) ); 522 | } 523 | } 524 | } 525 | 526 | if (empty_key && !only_values) 527 | { 528 | if (time_sec || time_full) 529 | print_time( key->timestamp, time_full, TRUE ); 530 | if (hex_type) 531 | printf( "%20c", ' ' ); 532 | printf( "%s\n", full ); 533 | } 534 | 535 | if (leave_key) 536 | *leave_key = FALSE; 537 | } 538 | 539 | 540 | int main( int argc, char* argv[] ) 541 | { 542 | char path[0x4000]; 543 | char* data; 544 | base_block* regf; 545 | FILE* f; 546 | int size; 547 | BOOL show_hive; 548 | int rc = 0; 549 | const char* errmsg; 550 | 551 | if (argc == 1 || strcmp( argv[1], "/?" ) == 0 552 | || strcmp( argv[1], "-?" ) == 0 553 | || strcmp( argv[1], "--help" ) == 0) 554 | { 555 | printf( "Dump a registry hive as text, one line per value.\n" 556 | "https://github.com/adoxa/regdump\n" 557 | "\n" 558 | "regdump [-hkstTv] HIVE...\n" 559 | "\n" 560 | "-h use hexadecimal for type & size, placed before key\n" 561 | "-k keys only (implies -t)\n" 562 | "-s include the entire string data (excluding trailing nulls)\n" 563 | "-t include key timestamp (seconds)\n" 564 | "-T include key timestamp (full resolution)\n" 565 | "-v values only\n" 566 | ); 567 | return 0; 568 | } 569 | 570 | while (argc > 1 && *argv[1] == '-') 571 | { 572 | while (*++argv[1]) 573 | { 574 | switch (*argv[1]) 575 | { 576 | case 'h': hex_type = TRUE; break; 577 | case 's': all_string = TRUE; break; 578 | case 'v': only_values = TRUE; break; 579 | case 'k': only_keys = TRUE; // fall through 580 | case 't': time_sec = TRUE; break; 581 | case 'T': time_full = TRUE; break; 582 | default: 583 | fprintf( stderr, "%c: unknown option.\n", *argv[1] ); 584 | return 1; 585 | } 586 | } 587 | ++argv; 588 | --argc; 589 | } 590 | 591 | full = path; 592 | show_hive = (argc > 2); 593 | 594 | for (; argc > 1; ++argv, --argc) 595 | { 596 | f = fopen( argv[1], "rb" ); 597 | if (!f) 598 | { 599 | perror( argv[1] ); 600 | rc = 1; 601 | continue; 602 | } 603 | 604 | if (fread( path, 4, 1, f ) != 1 || memcmp( path, "regf", 4 ) != 0) 605 | { 606 | errmsg = "invalid file ('regf' signature not found)"; 607 | error: 608 | fprintf( stderr, "%s: %s.\n", argv[1], errmsg ); 609 | fclose( f ); 610 | rc = 1; 611 | continue; 612 | } 613 | 614 | fseek( f, 0x1000, SEEK_SET ); 615 | if (fread( path, 4, 1, f ) != 1 || memcmp( path, "hbin", 4 ) != 0) 616 | { 617 | errmsg = "invalid file ('hbin' signature not found)"; 618 | goto error; 619 | } 620 | 621 | fseek( f, 0, SEEK_END ); 622 | size = ftell( f ); 623 | data = malloc( size ); 624 | if (!data) 625 | { 626 | errmsg = "insufficient memory"; 627 | goto error; 628 | } 629 | 630 | rewind( f ); 631 | if (fread( data, size, 1, f ) != 1) 632 | { 633 | free( data ); 634 | errmsg = "read error"; 635 | goto error; 636 | } 637 | fclose( f ); 638 | 639 | regf = (base_block*)data; 640 | big_data = (regf->major_version > 1 || regf->minor_version > 3); 641 | 642 | if (show_hive) 643 | printf( "%s\n\n", argv[1] ); 644 | 645 | // We just skip header and start walking root key tree. 646 | root = data + 0x1000; 647 | walk( path, (key_block*)(regf->root_cell_offset + root) ); 648 | free( data ); 649 | 650 | if (show_hive && argc > 2) 651 | putchar( '\n' ); 652 | } 653 | 654 | return rc; 655 | } 656 | --------------------------------------------------------------------------------