lnk_extractor.c (25222B)
1 /* 2 This file is part of libextractor. 3 Copyright (C) 2026 Vidyut Samanta and Christian Grothoff 4 5 libextractor is free software; you can redistribute it and/or modify 6 it under the terms of the GNU General Public License as published 7 by the Free Software Foundation; either version 3, or (at your 8 option) any later version. 9 10 libextractor is distributed in the hope that it will be useful, but 11 WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 General Public License for more details. 14 15 You should have received a copy of the GNU General Public License 16 along with libextractor; see the file COPYING. If not, write to the 17 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 18 Boston, MA 02110-1301, USA. 19 */ 20 /** 21 * @file plugins/lnk_extractor.c 22 * @brief plugin to support Windows shell links (.lnk shortcuts) 23 * @author Christian Grothoff 24 * 25 * A shell link describes a file that is somewhere else, and in doing so 26 * it records the machine that file was on: the volume serial number and 27 * label, the target's own timestamps and size, and -- in the link 28 * tracker block Windows adds so that a moved target can be found again 29 * -- the NetBIOS name of the creating machine and a pair of object 30 * identifiers. Those identifiers are version 1 UUIDs, so their node 31 * field is the MAC address of the network card that generated them. 32 * None of that survives in the file the link points at, which is why a 33 * .lnk is worth more to an investigator than its 2 KB suggest. 34 * 35 * Reference: [MS-SHLLINK], "Shell Link (.LNK) Binary File Format", 36 * https://learn.microsoft.com/openspecs/windows_protocols/ms-shllink/ 37 */ 38 #include "platform.h" 39 #include "extractor.h" 40 #include "forensics.h" 41 42 43 /** 44 * Size of the ShellLinkHeader, and the value its first field must have. 45 */ 46 #define LNK_HEADER_SIZE 0x4C 47 48 /** 49 * How much of the file we read. Shell links are a couple of kilobytes; 50 * anything past this is a property store or an icon we do not parse. 51 */ 52 #define LNK_MAX_READ (64 * 1024) 53 54 /** 55 * Most ExtraData blocks we walk. 56 */ 57 #define LNK_MAX_BLOCKS 32 58 59 /** 60 * Longest path we assemble from a base and a suffix, in bytes. 61 */ 62 #define LNK_MAX_PATH 1024 63 64 /* LinkFlags */ 65 #define LNK_HAS_ID_LIST 0x00000001 66 #define LNK_HAS_LINK_INFO 0x00000002 67 #define LNK_HAS_NAME 0x00000004 68 #define LNK_HAS_RELATIVE_PATH 0x00000008 69 #define LNK_HAS_WORKING_DIR 0x00000010 70 #define LNK_HAS_ARGUMENTS 0x00000020 71 #define LNK_HAS_ICON_LOCATION 0x00000040 72 #define LNK_IS_UNICODE 0x00000080 73 74 75 /** 76 * The class identifier every shell link starts with, 77 * 00021401-0000-0000-C000-000000000046, as it is stored: the first 78 * three fields little-endian, the rest in order. 79 */ 80 static const unsigned char lnk_clsid[16] = { 81 0x01, 0x14, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 82 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46 83 }; 84 85 86 /** 87 * Name of a drive type as recorded in a VolumeID. 88 * 89 * @param type the raw field 90 * @return a static string, NULL if the value is not one we know 91 */ 92 static const char * 93 lnk_drive_type (uint32_t type) 94 { 95 switch (type) 96 { 97 case 0: 98 return "unknown"; 99 case 1: 100 return "no root directory"; 101 case 2: 102 return "removable"; 103 case 3: 104 return "fixed disk"; 105 case 4: 106 return "network share"; 107 case 5: 108 return "CD-ROM"; 109 case 6: 110 return "RAM disk"; 111 default: 112 return NULL; 113 } 114 } 115 116 117 /** 118 * Append @a what to the comma-separated list in @a buf. 119 * 120 * @param buf destination buffer 121 * @param size number of bytes in @a buf 122 * @param what text to append 123 */ 124 static void 125 lnk_append (char *buf, 126 size_t size, 127 const char *what) 128 { 129 size_t used = strlen (buf); 130 size_t need = strlen (what); 131 132 if (0 != used) 133 { 134 if (used + 2 >= size) 135 return; 136 buf[used++] = ','; 137 buf[used++] = ' '; 138 buf[used] = '\0'; 139 } 140 if (used + need + 1 > size) 141 return; 142 memcpy (&buf[used], 143 what, 144 need + 1); 145 } 146 147 148 /** 149 * Emit a path made of a base and a suffix, either of which may be 150 * empty. 151 * 152 * [MS-SHLLINK] splits the target path into a part that identifies the 153 * volume and a part that is common to the local and the network 154 * spelling, and both halves are NUL-terminated in the file. Joining 155 * them here keeps the caller from having to care. 156 * 157 * @param ec extraction context 158 * @param type meta data type to report under 159 * @param base first half 160 * @param base_len number of bytes readable at @a base 161 * @param suffix second half, NULL for none 162 * @param suffix_len number of bytes readable at @a suffix 163 * @param unicode 1 if both halves are UTF-16LE, 0 for single bytes 164 * @return 1 if the caller should stop extracting, 0 to continue 165 */ 166 static int 167 lnk_emit_path (struct EXTRACTOR_ExtractContext *ec, 168 enum EXTRACTOR_MetaType type, 169 const unsigned char *base, 170 size_t base_len, 171 const unsigned char *suffix, 172 size_t suffix_len, 173 int unicode) 174 { 175 unsigned char buf[LNK_MAX_PATH]; 176 size_t out = 0; 177 size_t step = unicode ? 2 : 1; 178 179 if ( (NULL == base) || 180 (0 == base_len) ) 181 return 0; 182 while ( (out + step <= base_len) && 183 (out + step <= sizeof (buf)) ) 184 { 185 if (unicode) 186 { 187 if (0 == EXTRACTOR_forensic_le16_ (&base[out])) 188 break; 189 } 190 else if ('\0' == base[out]) 191 { 192 break; 193 } 194 buf[out] = base[out]; 195 if (unicode) 196 buf[out + 1] = base[out + 1]; 197 out += step; 198 } 199 if (NULL != suffix) 200 { 201 size_t i = 0; 202 203 while ( (i + step <= suffix_len) && 204 (out + step <= sizeof (buf)) ) 205 { 206 if (unicode) 207 { 208 if (0 == EXTRACTOR_forensic_le16_ (&suffix[i])) 209 break; 210 } 211 else if ('\0' == suffix[i]) 212 { 213 break; 214 } 215 buf[out] = suffix[i]; 216 if (unicode) 217 buf[out + 1] = suffix[i + 1]; 218 out += step; 219 i += step; 220 } 221 } 222 if (0 == out) 223 return 0; 224 if (unicode) 225 return EXTRACTOR_forensic_emit_utf16le_ (ec, 226 "lnk", 227 type, 228 buf, 229 out); 230 return EXTRACTOR_forensic_emit_text_ (ec, 231 "lnk", 232 type, 233 (const char *) buf, 234 out); 235 } 236 237 238 /** 239 * Report the volume the target lives on. 240 * 241 * The drive serial number is assigned when the volume is formatted and 242 * is the cheapest way to tell whether two shortcuts came off the same 243 * disk. 244 * 245 * @param ec extraction context 246 * @param v start of the VolumeID structure 247 * @param len number of bytes readable at @a v 248 * @return 1 if the caller should stop extracting, 0 to continue 249 */ 250 static int 251 lnk_parse_volume_id (struct EXTRACTOR_ExtractContext *ec, 252 const unsigned char *v, 253 size_t len) 254 { 255 uint32_t size; 256 uint32_t drive_type; 257 uint32_t serial; 258 uint32_t label; 259 const char *name; 260 261 if (len < 0x10) 262 return 0; 263 size = EXTRACTOR_forensic_le32_ (&v[0]); 264 if (size < 0x10) 265 return 0; 266 if (size < len) 267 len = size; 268 drive_type = EXTRACTOR_forensic_le32_ (&v[4]); 269 serial = EXTRACTOR_forensic_le32_ (&v[8]); 270 label = EXTRACTOR_forensic_le32_ (&v[12]); 271 if (0 != serial) 272 { 273 /* The spelling Windows itself uses in `dir' and `vol'. */ 274 if (0 != 275 EXTRACTOR_forensic_emit_ (ec, 276 "lnk", 277 EXTRACTOR_METATYPE_VOLUME_SERIAL, 278 "%04X-%04X", 279 (unsigned int) (serial >> 16), 280 (unsigned int) (serial & 0xFFFF))) 281 return 1; 282 } 283 if (NULL != (name = lnk_drive_type (drive_type))) 284 { 285 if (0 != 286 EXTRACTOR_forensic_emit_ (ec, 287 "lnk", 288 EXTRACTOR_METATYPE_FILESYSTEM_TYPE, 289 "%s", 290 name)) 291 return 1; 292 } 293 if (0x14 == label) 294 { 295 /* The ANSI label is still there, but the unicode one supersedes it. */ 296 uint32_t wide; 297 298 if (len < 0x14) 299 return 0; 300 wide = EXTRACTOR_forensic_le32_ (&v[0x10]); 301 if ( (wide < len) && 302 (wide >= 0x14) ) 303 return EXTRACTOR_forensic_emit_utf16le_ (ec, 304 "lnk", 305 EXTRACTOR_METATYPE_VOLUME_NAME, 306 &v[wide], 307 len - wide); 308 return 0; 309 } 310 if ( (label < len) && 311 (label >= 0x10) ) 312 return EXTRACTOR_forensic_emit_text_ (ec, 313 "lnk", 314 EXTRACTOR_METATYPE_VOLUME_NAME, 315 (const char *) &v[label], 316 len - label); 317 return 0; 318 } 319 320 321 /** 322 * Report what the LinkInfo structure says about the target's location. 323 * 324 * @param ec extraction context 325 * @param li start of the LinkInfo structure 326 * @param len number of bytes readable at @a li 327 * @return 1 if the caller should stop extracting, 0 to continue 328 */ 329 static int 330 lnk_parse_link_info (struct EXTRACTOR_ExtractContext *ec, 331 const unsigned char *li, 332 size_t len) 333 { 334 uint32_t header_size; 335 uint32_t flags; 336 uint32_t suffix_off; 337 const unsigned char *suffix = NULL; 338 size_t suffix_len = 0; 339 int wide = 0; 340 341 if (len < 0x1C) 342 return 0; 343 header_size = EXTRACTOR_forensic_le32_ (&li[4]); 344 flags = EXTRACTOR_forensic_le32_ (&li[8]); 345 /* A header of 0x24 or more means the unicode spellings of the two 346 path halves are present and are the ones to prefer. */ 347 if ( (header_size >= 0x24) && 348 (len >= 0x24) ) 349 wide = 1; 350 suffix_off = wide 351 ? EXTRACTOR_forensic_le32_ (&li[0x20]) 352 : EXTRACTOR_forensic_le32_ (&li[0x18]); 353 if ( (0 != suffix_off) && 354 (suffix_off < len) ) 355 { 356 suffix = &li[suffix_off]; 357 suffix_len = len - suffix_off; 358 } 359 if (0 != (flags & 0x1)) 360 { 361 uint32_t vol_off = EXTRACTOR_forensic_le32_ (&li[0x0C]); 362 uint32_t base_off = wide 363 ? EXTRACTOR_forensic_le32_ (&li[0x1C]) 364 : EXTRACTOR_forensic_le32_ (&li[0x10]); 365 366 if ( (0 != vol_off) && 367 (vol_off < len) && 368 (0 != 369 lnk_parse_volume_id (ec, 370 &li[vol_off], 371 len - vol_off)) ) 372 return 1; 373 if ( (0 != base_off) && 374 (base_off < len) && 375 (0 != 376 lnk_emit_path (ec, 377 EXTRACTOR_METATYPE_TARGET_PATH, 378 &li[base_off], 379 len - base_off, 380 suffix, 381 suffix_len, 382 wide)) ) 383 return 1; 384 } 385 if (0 != (flags & 0x2)) 386 { 387 uint32_t cnrl_off = EXTRACTOR_forensic_le32_ (&li[0x14]); 388 const unsigned char *cnrl; 389 size_t cnrl_len; 390 uint32_t net_off; 391 int net_wide = 0; 392 393 if ( (0 == cnrl_off) || 394 (cnrl_off >= len) ) 395 return 0; 396 cnrl = &li[cnrl_off]; 397 cnrl_len = len - cnrl_off; 398 if (cnrl_len < 0x14) 399 return 0; 400 net_off = EXTRACTOR_forensic_le32_ (&cnrl[8]); 401 if ( (net_off > 0x14) && 402 (cnrl_len >= 0x18) ) 403 { 404 uint32_t wide_off = EXTRACTOR_forensic_le32_ (&cnrl[0x14]); 405 406 if ( (0 != wide_off) && 407 (wide_off < cnrl_len) ) 408 { 409 net_off = wide_off; 410 net_wide = 1; 411 } 412 } 413 if ( (0 != net_off) && 414 (net_off < cnrl_len) && 415 (0 != 416 lnk_emit_path (ec, 417 EXTRACTOR_METATYPE_TARGET_PATH, 418 &cnrl[net_off], 419 cnrl_len - net_off, 420 net_wide ? NULL : suffix, 421 net_wide ? 0 : suffix_len, 422 net_wide)) ) 423 return 1; 424 } 425 return 0; 426 } 427 428 429 /** 430 * Report a droid identifier, and the MAC address hiding in it. 431 * 432 * The two identifiers in a TrackerDataBlock are the NTFS object 433 * identifiers of the volume and of the file, generated as version 1 434 * UUIDs. A version 1 UUID ends in the node field, which the generator 435 * fills with the MAC address of a network interface unless it had none 436 * -- in which case it must set the multicast bit to say so. Checking 437 * both the version and that bit is what keeps a random identifier from 438 * being reported as somebody's hardware address. 439 * 440 * @param ec extraction context 441 * @param guid the 16 bytes 442 * @param[in,out] seen previously emitted identifier, to suppress the 443 * usual case where the birth and current values are the same 444 * @return 1 if the caller should stop extracting, 0 to continue 445 */ 446 static int 447 lnk_emit_droid (struct EXTRACTOR_ExtractContext *ec, 448 const unsigned char *guid, 449 unsigned char *seen) 450 { 451 if (0 == memcmp (seen, 452 guid, 453 16)) 454 return 0; 455 memcpy (seen, 456 guid, 457 16); 458 if (0 != 459 EXTRACTOR_forensic_emit_guid_ (ec, 460 "lnk", 461 EXTRACTOR_METATYPE_SYSTEM_IDENTIFIER, 462 guid, 463 1)) 464 return 1; 465 if (1 != (guid[7] >> 4)) 466 return 0; /* not a time-based UUID, so no node field */ 467 if (0 != (guid[10] & 0x01)) 468 return 0; /* the generator flagged the node as made up */ 469 if ( (0 == guid[10]) && (0 == guid[11]) && (0 == guid[12]) && 470 (0 == guid[13]) && (0 == guid[14]) && (0 == guid[15]) ) 471 return 0; 472 return EXTRACTOR_forensic_emit_ (ec, 473 "lnk", 474 EXTRACTOR_METATYPE_MAC_ADDRESS, 475 "%02x:%02x:%02x:%02x:%02x:%02x", 476 guid[10], guid[11], guid[12], 477 guid[13], guid[14], guid[15]); 478 } 479 480 481 /** 482 * Report a TrackerDataBlock. 483 * 484 * @param ec extraction context 485 * @param b start of the block 486 * @param len number of bytes readable at @a b 487 * @return 1 if the caller should stop extracting, 0 to continue 488 */ 489 static int 490 lnk_parse_tracker (struct EXTRACTOR_ExtractContext *ec, 491 const unsigned char *b, 492 size_t len) 493 { 494 unsigned char seen[16]; 495 496 if (len < 0x60) 497 return 0; 498 if (EXTRACTOR_forensic_le32_ (&b[8]) < 0x58) 499 return 0; /* the declared payload is too short to hold the fields */ 500 if (0 != 501 EXTRACTOR_forensic_emit_text_ (ec, 502 "lnk", 503 EXTRACTOR_METATYPE_SOURCE_HOST, 504 (const char *) &b[0x10], 505 16)) 506 return 1; 507 memset (seen, 508 0, 509 sizeof (seen)); 510 if (0 != 511 lnk_emit_droid (ec, 512 &b[0x20], 513 seen)) 514 return 1; /* DroidVolumeId */ 515 if (0 != 516 lnk_emit_droid (ec, 517 &b[0x40], 518 seen)) 519 return 1; /* BirthVolumeId */ 520 memset (seen, 521 0, 522 sizeof (seen)); 523 if (0 != 524 lnk_emit_droid (ec, 525 &b[0x30], 526 seen)) 527 return 1; /* DroidFileId */ 528 return lnk_emit_droid (ec, 529 &b[0x50], 530 seen); /* BirthFileId */ 531 } 532 533 534 /** 535 * Walk the ExtraData block chain at the end of the file. 536 * 537 * @param ec extraction context 538 * @param buf the file contents 539 * @param len number of bytes in @a buf 540 * @param pos offset of the first block 541 * @return 1 if the caller should stop extracting, 0 to continue 542 */ 543 static int 544 lnk_parse_extra (struct EXTRACTOR_ExtractContext *ec, 545 const unsigned char *buf, 546 size_t len, 547 size_t pos) 548 { 549 for (unsigned int n = 0; 550 (pos + 8 <= len) && (n < LNK_MAX_BLOCKS); 551 n++) 552 { 553 uint32_t size = EXTRACTOR_forensic_le32_ (&buf[pos]); 554 uint32_t sig = EXTRACTOR_forensic_le32_ (&buf[pos + 4]); 555 size_t have; 556 557 if (size < 8) 558 break; /* the terminal block, or a size that cannot progress */ 559 have = len - pos; 560 if (have > size) 561 have = size; 562 switch (sig) 563 { 564 case 0xA0000001U: /* EnvironmentVariableDataBlock */ 565 /* An expandable path, used when the target is behind an 566 environment variable. The unicode copy starts at 0x110. */ 567 if (have >= 0x314) 568 { 569 if (0 != 570 lnk_emit_path (ec, 571 EXTRACTOR_METATYPE_TARGET_PATH, 572 &buf[pos + 0x110], 573 0x204, 574 NULL, 575 0, 576 1)) 577 return 1; 578 } 579 else if (have >= 0x110) 580 { 581 if (0 != 582 lnk_emit_path (ec, 583 EXTRACTOR_METATYPE_TARGET_PATH, 584 &buf[pos + 0x0C], 585 0x104, 586 NULL, 587 0, 588 0)) 589 return 1; 590 } 591 break; 592 case 0xA0000003U: /* TrackerDataBlock */ 593 if (0 != 594 lnk_parse_tracker (ec, 595 &buf[pos], 596 have)) 597 return 1; 598 break; 599 case 0xA0000006U: /* DarwinDataBlock */ 600 /* The Windows Installer product code the shortcut belongs to. */ 601 if (have >= 0x314) 602 { 603 if (0 != 604 lnk_emit_path (ec, 605 EXTRACTOR_METATYPE_APPLICATION_ID, 606 &buf[pos + 0x110], 607 0x204, 608 NULL, 609 0, 610 1)) 611 return 1; 612 } 613 break; 614 default: 615 break; 616 } 617 pos += size; 618 } 619 return 0; 620 } 621 622 623 /** 624 * Main entry method for the shell link extraction plugin. 625 * 626 * @param ec extraction context provided to the plugin 627 */ 628 void 629 EXTRACTOR_lnk_extract_method (struct EXTRACTOR_ExtractContext *ec); 630 631 void 632 EXTRACTOR_lnk_extract_method (struct EXTRACTOR_ExtractContext *ec) 633 { 634 unsigned char head[LNK_HEADER_SIZE]; 635 unsigned char *buf; 636 uint64_t fsize; 637 size_t len; 638 size_t pos; 639 uint32_t flags; 640 uint32_t attrs; 641 uint32_t target_size; 642 uint64_t created; 643 uint64_t accessed; 644 uint64_t written; 645 int wide; 646 char list[256]; 647 static const struct 648 { 649 uint32_t flag; 650 enum EXTRACTOR_MetaType type; 651 } strings[] = { 652 { LNK_HAS_NAME, EXTRACTOR_METATYPE_DESCRIPTION }, 653 { LNK_HAS_RELATIVE_PATH, EXTRACTOR_METATYPE_TARGET_PATH }, 654 { LNK_HAS_WORKING_DIR, EXTRACTOR_METATYPE_WORKING_DIRECTORY }, 655 { LNK_HAS_ARGUMENTS, EXTRACTOR_METATYPE_COMMAND_LINE }, 656 { LNK_HAS_ICON_LOCATION, EXTRACTOR_METATYPE_URI } 657 }; 658 659 /* The magic is a fixed size and a fixed class identifier at offset 660 zero, so a non-match costs one short read. */ 661 if (! EXTRACTOR_forensic_read_ (ec, 662 0, 663 head, 664 sizeof (head))) 665 return; 666 if (LNK_HEADER_SIZE != EXTRACTOR_forensic_le32_ (&head[0])) 667 return; 668 if (0 != memcmp (&head[4], 669 lnk_clsid, 670 sizeof (lnk_clsid))) 671 return; 672 if (0 != 673 ec->proc (ec->cls, 674 "lnk", 675 EXTRACTOR_METATYPE_MIMETYPE, 676 EXTRACTOR_METAFORMAT_UTF8, 677 "text/plain", 678 "application/x-ms-shortcut", 679 strlen ("application/x-ms-shortcut") + 1)) 680 return; 681 flags = EXTRACTOR_forensic_le32_ (&head[0x14]); 682 attrs = EXTRACTOR_forensic_le32_ (&head[0x18]); 683 target_size = EXTRACTOR_forensic_le32_ (&head[0x34]); 684 wide = (0 != (flags & LNK_IS_UNICODE)); 685 /* These three are the target's timestamps as they were when the 686 shortcut was made, not the shortcut's own. */ 687 created = EXTRACTOR_forensic_le64_ (&head[0x1C]); 688 accessed = EXTRACTOR_forensic_le64_ (&head[0x24]); 689 written = EXTRACTOR_forensic_le64_ (&head[0x2C]); 690 if (0 != 691 EXTRACTOR_forensic_emit_filetime_ (ec, 692 "lnk", 693 EXTRACTOR_METATYPE_CREATION_DATE, 694 created)) 695 return; 696 if (0 != 697 EXTRACTOR_forensic_emit_filetime_ (ec, 698 "lnk", 699 EXTRACTOR_METATYPE_ACCESS_DATE, 700 accessed)) 701 return; 702 if (0 != 703 EXTRACTOR_forensic_emit_filetime_ (ec, 704 "lnk", 705 EXTRACTOR_METATYPE_MODIFICATION_DATE, 706 written)) 707 return; 708 if ( (0 != target_size) && 709 (0 != 710 EXTRACTOR_forensic_emit_size_ (ec, 711 "lnk", 712 EXTRACTOR_METATYPE_EMBEDDED_FILE_SIZE, 713 target_size)) ) 714 return; 715 list[0] = '\0'; 716 if (0 != (attrs & 0x0001)) 717 lnk_append (list, sizeof (list), "READONLY"); 718 if (0 != (attrs & 0x0002)) 719 lnk_append (list, sizeof (list), "HIDDEN"); 720 if (0 != (attrs & 0x0004)) 721 lnk_append (list, sizeof (list), "SYSTEM"); 722 if (0 != (attrs & 0x0010)) 723 lnk_append (list, sizeof (list), "DIRECTORY"); 724 if (0 != (attrs & 0x0020)) 725 lnk_append (list, sizeof (list), "ARCHIVE"); 726 if (0 != (attrs & 0x0080)) 727 lnk_append (list, sizeof (list), "NORMAL"); 728 if (0 != (attrs & 0x0100)) 729 lnk_append (list, sizeof (list), "TEMPORARY"); 730 if (0 != (attrs & 0x0200)) 731 lnk_append (list, sizeof (list), "SPARSE"); 732 if (0 != (attrs & 0x0400)) 733 lnk_append (list, sizeof (list), "REPARSE_POINT"); 734 if (0 != (attrs & 0x0800)) 735 lnk_append (list, sizeof (list), "COMPRESSED"); 736 if (0 != (attrs & 0x1000)) 737 lnk_append (list, sizeof (list), "OFFLINE"); 738 if (0 != (attrs & 0x2000)) 739 lnk_append (list, sizeof (list), "NOT_CONTENT_INDEXED"); 740 if (0 != (attrs & 0x4000)) 741 lnk_append (list, sizeof (list), "ENCRYPTED"); 742 if ( ('\0' != list[0]) && 743 (0 != 744 EXTRACTOR_forensic_emit_ (ec, 745 "lnk", 746 EXTRACTOR_METATYPE_ATTRIBUTES, 747 "%s", 748 list)) ) 749 return; 750 751 fsize = ec->get_size (ec->cls); 752 if ( (0 == fsize) || 753 (UINT64_MAX == fsize) ) 754 return; 755 len = (fsize > LNK_MAX_READ) ? LNK_MAX_READ : (size_t) fsize; 756 if (len <= LNK_HEADER_SIZE) 757 return; 758 if (NULL == (buf = malloc (len))) 759 return; 760 if (! EXTRACTOR_forensic_read_ (ec, 761 0, 762 buf, 763 len)) 764 { 765 free (buf); 766 return; 767 } 768 pos = LNK_HEADER_SIZE; 769 if (0 != (flags & LNK_HAS_ID_LIST)) 770 { 771 size_t id_size; 772 773 if (pos + 2 > len) 774 goto cleanup; 775 id_size = EXTRACTOR_forensic_le16_ (&buf[pos]); 776 pos += 2; 777 if (id_size > len - pos) 778 goto cleanup; 779 pos += id_size; 780 } 781 if (0 != (flags & LNK_HAS_LINK_INFO)) 782 { 783 uint32_t li_size; 784 size_t have; 785 786 if (pos + 4 > len) 787 goto cleanup; 788 li_size = EXTRACTOR_forensic_le32_ (&buf[pos]); 789 if (li_size < 0x1C) 790 goto cleanup; 791 have = len - pos; 792 if (have > li_size) 793 have = li_size; 794 if (0 != 795 lnk_parse_link_info (ec, 796 &buf[pos], 797 have)) 798 goto cleanup; 799 if (li_size > len - pos) 800 goto cleanup; 801 pos += li_size; 802 } 803 for (unsigned int i = 0; i < sizeof (strings) / sizeof (strings[0]); i++) 804 { 805 size_t count; 806 size_t bytes; 807 808 if (0 == (flags & strings[i].flag)) 809 continue; 810 if (pos + 2 > len) 811 goto cleanup; 812 count = EXTRACTOR_forensic_le16_ (&buf[pos]); 813 pos += 2; 814 bytes = wide ? count * 2 : count; 815 if (bytes > len - pos) 816 goto cleanup; 817 if (0 != bytes) 818 { 819 int stop; 820 821 if (wide) 822 stop = EXTRACTOR_forensic_emit_utf16le_ (ec, 823 "lnk", 824 strings[i].type, 825 &buf[pos], 826 bytes); 827 else 828 stop = EXTRACTOR_forensic_emit_text_ (ec, 829 "lnk", 830 strings[i].type, 831 (const char *) &buf[pos], 832 bytes); 833 if (0 != stop) 834 goto cleanup; 835 } 836 pos += bytes; 837 } 838 (void) lnk_parse_extra (ec, 839 buf, 840 len, 841 pos); 842 cleanup: 843 free (buf); 844 } 845 846 847 /* end of lnk_extractor.c */