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