1 /**
2  * \addtogroup winlnk
3  * @{
4  */
5 /**
6  * \file
7  *
8  * scan_winlnk finds windows LNK files
9  *
10  * Revision history:
11  * - 2014-apr-30 slg - created
12  * - 2015-apr-08 bda - scan nested data structures per MS-SHLLINK Doc
13  *                     in particualr to capture remote links
14  *
15  * Resources:
16  * - http://msdn.microsoft.com/en-us/library/dd871305.aspx
17  * - https://forensicswiki.xyz/wiki/index.php?title=LNK
18  */
19 
20 #include "config.h"
21 
22 #include <iostream>
23 #include <fstream>
24 #include <string>
25 #include <stdlib.h>
26 #include <strings.h>
27 #include <string.h>
28 #include <stdint.h>
29 #include <sstream>
30 #include <vector>
31 
32 #include "utf8.h"
33 #include "be13_api/utils.h"              // needs config.h
34 #include "be13_api/scanner_params.h"
35 #include "be13_api/unicode_escape.h"
36 #include "dfxml_cpp/src/dfxml_writer.h"
37 
38 static const size_t SMALLEST_LNK_FILE = 150;  // did you see smaller LNK file?
39 
40 /* Extract and form GUID. Needs 16 bytes */
get_guid(const sbuf_t & buf,const size_t offset)41 std::string get_guid(const sbuf_t &buf, const size_t offset)
42 {
43     char str[37];
44     snprintf(str, 37, "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", \
45         buf[offset+3], buf[offset+2], buf[offset+1], buf[offset+0],    \
46         buf[offset+5], buf[offset+4], buf[offset+7], buf[offset+6],    \
47         buf[offset+8], buf[offset+9], buf[offset+10], buf[offset+11],    \
48         buf[offset+12], buf[offset+13], buf[offset+14], buf[offset+15]);
49     return(std::string(str));
50 }
51 
52 /*
53  * This is called a lot, so we take a const ref to the sbuf and an offset.
54  */
read_StringData(const std::string & tagname,const sbuf_t & sbuf,size_t offset,const bool is_unicode,dfxml_writer::strstrmap_t & lnkmap)55 size_t read_StringData(const std::string& tagname, const sbuf_t &sbuf, size_t offset,
56                        const bool is_unicode, dfxml_writer::strstrmap_t& lnkmap)
57 {
58     // get count to read
59     const uint16_t count = sbuf.get16u(offset+0);
60     const size_t size = 2 + count * (is_unicode ? 2 : 1);
61 
62     if (count >= 4000) {
63         // skip unreasonably long count
64         return size;
65     }
66 
67     if (is_unicode) {
68         // get string data from UTF16 input
69         std::wstring utf16_data = sbuf.getUTF16(offset+2, count);
70         std::string data = safe_utf16to8(utf16_data);
71         if (data.size()==0) data="INVALID_DATA";
72         lnkmap[tagname] = data;
73     } else {
74         // get string data from UTF8 input
75         std::string utf8_string = sbuf.getUTF8(offset+2, count);
76         validateOrEscapeUTF8(utf8_string,true,false,false);
77         if (utf8_string.size()==0) utf8_string="INVALID_DATA";
78         lnkmap[tagname] = utf8_string;
79     }
80     return size;
81 }
82 
read_utf8(const std::string & tagname,const sbuf_t & sbuf,size_t offset,dfxml_writer::strstrmap_t & lnkmap)83 void read_utf8(const std::string& tagname, const sbuf_t &sbuf, size_t offset, dfxml_writer::strstrmap_t& lnkmap) {
84     std::string utf8_string = sbuf.getUTF8(offset);
85     validateOrEscapeUTF8(utf8_string,true,false,false);
86     // there can be fields with size 0 so skip them
87     if (utf8_string.size()>0) {
88         lnkmap[tagname] = utf8_string;
89     }
90 }
91 
read_utf16(const std::string & tagname,const sbuf_t & sbuf,size_t offset,dfxml_writer::strstrmap_t & lnkmap)92 void read_utf16(const std::string& tagname, const sbuf_t &sbuf, size_t offset, dfxml_writer::strstrmap_t& lnkmap) {
93     std::wstring utf16_string = sbuf.getUTF16(offset);
94     std::string utf8_string = safe_utf16to8(utf16_string);
95     if (utf8_string.size()>0) {
96         lnkmap[tagname] = utf8_string;
97     }
98 }
99 
read_LinkTargetIDList(const sbuf_t & sbuf,size_t offset,dfxml_writer::strstrmap_t & lnkmap)100 size_t read_LinkTargetIDList(const sbuf_t &sbuf, size_t offset, dfxml_writer::strstrmap_t& lnkmap) {
101     // there are no fields to record in this structure so just advance by size
102     const uint16_t size = sbuf.get16u(offset+0);
103     return (size_t)size;
104 }
105 
read_VolumeID(const sbuf_t & sbuf,size_t offset,dfxml_writer::strstrmap_t & lnkmap)106 void read_VolumeID(const sbuf_t &sbuf, size_t offset, dfxml_writer::strstrmap_t& lnkmap) {
107     const uint32_t VolumeLabelOffset = sbuf.get32u(offset+12);
108     if (VolumeLabelOffset == 0x14) {
109         const uint32_t VolumeLabelOffsetUnicode = sbuf.get32u(offset+16);
110         read_utf16("volume_label", sbuf, offset+VolumeLabelOffsetUnicode, lnkmap);
111     } else {
112         read_utf8("volume_label", sbuf, offset+VolumeLabelOffset, lnkmap);
113     }
114 }
115 
read_CommonNetworkRelativeLink(const sbuf_t & sbuf,size_t offset,dfxml_writer::strstrmap_t & lnkmap)116 void read_CommonNetworkRelativeLink(const sbuf_t &sbuf, size_t offset, dfxml_writer::strstrmap_t& lnkmap) {
117 
118     // data state
119     //const uint32_t CommonNetworkRelativeLinkSize = sbuf.get32u(0);
120     const uint32_t CommonNetworkRelativeLinkFlags = sbuf.get32u(offset+4);
121 
122     // NetName
123     const uint32_t NetNameOffset = sbuf.get32u(offset+8);
124     read_utf8("net_name", sbuf, offset+NetNameOffset, lnkmap);
125 
126     // device name
127     if (CommonNetworkRelativeLinkFlags & (1 << 0)) {    // A ValidDevice
128         const uint32_t DeviceNameOffset = sbuf.get32u(offset+12);
129         read_utf8("device_name", sbuf, offset+DeviceNameOffset, lnkmap);
130     }
131 
132     // network provider type
133     // this field is currently not reported.
134 
135     // NetNameOffsetUnicode
136     if (NetNameOffset > 0x14) {
137         const uint32_t NetNameOffsetUnicode = sbuf.get32u(offset+20);
138         read_utf16("net_name_unicode", sbuf, offset+NetNameOffsetUnicode, lnkmap);
139     }
140 
141     // DeviceNameOffsetUnicode
142     if (NetNameOffset > 0x14) {
143         const uint32_t DeviceNameOffsetUnicode = sbuf.get32u(offset+24);
144         read_utf16("device_name_unicode", sbuf, offset+DeviceNameOffsetUnicode, lnkmap);
145     }
146 }
147 
read_LinkInfo(const sbuf_t & sbuf,size_t offset,dfxml_writer::strstrmap_t & lnkmap)148 size_t read_LinkInfo(const sbuf_t &sbuf, size_t offset, dfxml_writer::strstrmap_t& lnkmap) {
149 
150     // data state
151     const uint32_t LinkInfoSize = sbuf.get32u(offset+0);
152 
153     const uint32_t LinkInfoHeaderSize = sbuf.get32u(offset+4);
154     const uint32_t LinkInfoFlags = sbuf.get32u(offset+8);
155     const uint32_t VolumeIDOffset = sbuf.get32u(offset+12);
156 
157     // volume ID
158     if (LinkInfoFlags & (1 << 0)) {    // A VolumeIDAndLocalBasePath
159         read_VolumeID(sbuf, offset+VolumeIDOffset, lnkmap);
160     }
161 
162     // local base path
163     if (LinkInfoFlags & (1 << 0)) {    // A VolumeIDAndLocalBasePath
164         const uint32_t LocalBasePathOffset = sbuf.get32u(offset+16);
165         read_utf8("local_base_path", sbuf, offset+LocalBasePathOffset, lnkmap);
166     }
167 
168     // common network relative link
169     if (LinkInfoFlags & (1 << 1)) {    // B CommonNetworkRelativeLinkAndPathSuffix
170         const uint32_t CommonNetworkRelativeLinkOffset = sbuf.get32u(offset+20);
171         read_CommonNetworkRelativeLink(sbuf, offset+CommonNetworkRelativeLinkOffset, lnkmap);
172     }
173 
174     // common path suffix
175     const uint32_t CommonPathSuffixOffset = sbuf.get32u(offset+24);
176     read_utf8("common_path_suffix", sbuf, offset+CommonPathSuffixOffset, lnkmap);
177 
178     // local base path unicode
179     if (LinkInfoFlags & (1 << 0)) {    // A VolumeIDAndLocalBasePath
180         if (LinkInfoHeaderSize >=0x24) {
181             const uint32_t LocalBasePathOffsetUnicode = sbuf.get32u(offset+28);
182             read_utf16("local_base_path_unicode", sbuf, offset+LocalBasePathOffsetUnicode, lnkmap);
183         }
184     }
185 
186     // common path suffix unicode
187     if (LinkInfoHeaderSize >=0x24) {
188         const uint32_t CommonPathSuffixOffsetUnicode = sbuf.get32u(offset+32);
189         read_utf16("common_path_suffix_unicode", sbuf, offset+CommonPathSuffixOffsetUnicode, lnkmap);
190     }
191 
192     return LinkInfoSize;
193 }
194 
195 // top level data structrue, true if has data
196 // This one doesn't take an offset; we make a child sbuf because we use it a lot
read_ShellLinkHeader(const sbuf_t & sbuf,size_t pos,dfxml_writer::strstrmap_t & lnkmap)197 bool read_ShellLinkHeader(const sbuf_t &sbuf, size_t pos, dfxml_writer::strstrmap_t& lnkmap)
198 {
199     sbuf_t sb2 = sbuf.slice(pos);
200     // record fields in this header
201     const uint64_t CreationTime   = sb2.get64u(0x001c);
202     const uint64_t AccessTime     = sb2.get64u(0x0024);
203     const uint64_t WriteTime      = sb2.get64u(0x002c);
204     lnkmap["ctime"] = microsoftDateToISODate(CreationTime);
205     lnkmap["atime"] = microsoftDateToISODate(AccessTime);
206     lnkmap["wtime"] = microsoftDateToISODate(WriteTime);
207 
208     // flags dictating how the structure will be parsed
209     const uint32_t LinkFlags      = sb2.get32u(0x0014);
210     const bool is_unicode         = LinkFlags & (1 << 7);
211 
212     // read the optional fields
213     size_t offset = 0x004c;
214     if (LinkFlags & (1 << 0)) {    // LinkFlags A HasLinkTargetIDList
215         offset += read_LinkTargetIDList(sb2, offset, lnkmap) + 2;
216     }
217 
218     // If I return here, it doesn't crash
219 
220     if (LinkFlags & (1 << 1)) {    // LinkFlags B HasLinkInfo
221         offset += read_LinkInfo(sb2, offset, lnkmap); // causing crash
222     }
223 
224     // what if we return here?
225     return false;                       // remove me
226 
227 
228     if (LinkFlags & (1 << 2)) {    // LinkFlags C HasName
229         offset += read_StringData("name_string", sb2, offset, is_unicode, lnkmap);
230     }
231     if (LinkFlags & (1 << 3)) {    // LinkFlags D HasRelativePath
232         offset += read_StringData("relative_path", sb2, offset, is_unicode, lnkmap);
233     }
234     if (LinkFlags & (1 << 4)) {    // LinkFlags E HasWorkingDir
235         offset += read_StringData("working_dir", sb2, offset, is_unicode, lnkmap);
236     }
237     if (LinkFlags & (1 << 5)) {    // LinkFlags F HasArguments
238         offset += read_StringData("command_line_arguments", sb2, offset, is_unicode, lnkmap);
239     }
240     if (LinkFlags & (1 << 6)) {    // LinkFlags G HasIconLocation
241         offset += read_StringData("icon_location", sb2, offset, is_unicode, lnkmap);
242     }
243 
244     int lkcount = 0;
245     while (lkcount < 20) { // there should be at most 11 of these
246 
247         // read any extra data blocks
248         const uint32_t BlockSize = sb2.get32u(offset+0);
249         if (BlockSize < 4) {
250             // the end block is defined as having BlockSize<4
251             break;
252         }
253 
254         // only some block types are interesting
255         const uint32_t BlockSignature = sb2.get32u(offset+4);
256         if (BlockSize == 0x60 && BlockSignature == 0xa0000003) {
257 
258             // Tracker Data Block
259             std::string dvolid = get_guid(sb2, offset+32);
260             lnkmap["droid_volumeid"] = dvolid;
261             lnkmap["droid_fileid"] = get_guid(sb2, offset+48);
262             lnkmap["birth_volumeid"] = get_guid(sb2, offset+64);
263             lnkmap["birth_fileid"] = get_guid(sb2, offset+80);
264         }
265 
266         offset += BlockSize;
267         lkcount += 1;
268     }
269 
270     // sometimes blank featues exist
271     if (LinkFlags == 0 && lkcount == 0) {
272         return false;        // blank
273     } else {
274 
275         return true;        // has data
276     }
277 }
278 
279 // return existing field else ""
280 // make this more efficient? It's pretty lame.
281 // On the other hand, it works and it should only be envoked for links when we find them.
get_field(const dfxml_writer::strstrmap_t & lnkmap,const std::string & name)282 std::string get_field(const dfxml_writer::strstrmap_t& lnkmap, const std::string& name) {
283     return lnkmap.find(name) == lnkmap.end() ? "" : lnkmap.find(name)->second;
284 }
285 
286 /**
287  * Scanner scan_winlnk scans and extracts windows lnk records.
288  * It takes scanner_params and recursion_control_block as input.
289  * The scanner_params structure includes the following:
290  * \li scanner_params::phase identifying the scanner phase.
291  * \li scanner_params.sbuf containing the buffer being scanned.
292  * \li scanner_params.fs, which provides feature recorder feature_recorder
293  * that scan_winlnk will write to.
294  *
295  * scan_winlnk iterates through each byte of sbuf
296  */
297 static feature_recorder *winlnk_recorder = nullptr;
298 
299 extern "C"
scan_winlnk(scanner_params & sp)300 void scan_winlnk(scanner_params &sp)
301 {
302     sp.check_version();
303     if (sp.phase==scanner_params::PHASE_INIT){
304         sp.info->set_name("winlnk");
305         sp.info->author		= "Simson Garfinkel";
306         sp.info->description	= "Search for Windows LNK files";
307         sp.info->feature_defs.push_back( feature_recorder_def("winlnk"));
308         sp.info->scanner_flags.scanner_wants_filesystems = true;
309         sp.info->min_sbuf_size = SMALLEST_LNK_FILE;
310         return;
311     }
312     if (sp.phase==scanner_params::PHASE_INIT2){
313         winlnk_recorder = &sp.named_feature_recorder("winlnk");
314     }
315 
316     if (sp.phase==scanner_params::PHASE_SCAN){
317 
318 	// phase 1: set up the feature recorder and search for winlnk features
319 	const sbuf_t &sbuf = *(sp.sbuf);
320 
321         for (size_t pos=0;(pos < sbuf.pagesize) &&  (pos + SMALLEST_LNK_FILE < sbuf.bufsize ); pos++){
322 
323             // look for Shell Link (.LNK) binary file format magic number
324             // Move this to a b-m search.
325             if ( sbuf.get32u(pos+0x00) == 0x0000004c &&      // header size
326                  sbuf.get32u(pos+0x04) == 0x00021401 &&      // LinkCLSID 1
327                  sbuf.get32u(pos+0x08) == 0x00000000 &&      // LinkCLSID 2
328                  sbuf.get32u(pos+0x0c) == 0x000000c0 &&      // LinkCLSID 3
329                  sbuf.get32u(pos+0x10) == 0x46000000 ){      // LinkCLSID 4
330 
331                 // container for reported fields
332 		dfxml_writer::strstrmap_t lnkmap;
333 
334                 // read
335                 bool has_data = true;
336                 try {
337                     has_data = read_ShellLinkHeader(sbuf, pos, lnkmap);
338                 } catch (sbuf_t::range_exception_t &e) {
339                     // add error field to indicate that the read was not complete
340                     lnkmap["error"] = "LINKINFO_DATA_ERROR";
341                 }
342 
343                 // set path when no data
344                 std::string path = "";
345                 if (!has_data) {
346                     path = "NO_LINKINFO";
347                 }
348 
349                 // pick a path value from one of the lnkmap fields
350                 if (path == "") path = get_field(lnkmap, "local_base_path");
351                 if (path == "") path = get_field(lnkmap, "local_base_path_unicode");
352                 if (path == "") path = get_field(lnkmap, "common_path_suffix");
353                 if (path == "") path = get_field(lnkmap, "common_path_suffix_unicode");
354                 if (path == "") path = get_field(lnkmap, "name_string");
355                 if (path == "") path = get_field(lnkmap, "relative_path");
356                 if (path == "") path = get_field(lnkmap, "net_name");
357                 if (path == "") path = get_field(lnkmap, "net_name_unicode");
358                 if (path == "") path = get_field(lnkmap, "device_name");
359                 if (path == "") path = get_field(lnkmap, "device_name_unicode");
360                 if (path == "") path = get_field(lnkmap, "working_dir");
361                 if (path == "") path = get_field(lnkmap, "command_line_arguments");
362                 if (path == "") path = get_field(lnkmap, "volume_label");
363                 if (path == "") path = get_field(lnkmap, "droid_volumeid");
364                 if (path == "") path = "LINKINFO_PATH_EMPTY"; // nothing to assign to path
365 
366                 // record
367                 winlnk_recorder->write(sbuf.pos0+pos, path, dfxml_writer::xmlmap(lnkmap,"lnk",""));
368             }
369         }
370     }
371 }
372