通过快捷方式lnk获得文件真实路径

通过快捷方式.lnk获得文件真实路径

前提

最近开发资源管理,需要预先上传大量资源,负责整理资源的同学因为空间不足,直接用快捷键方式整理视频资源OTZ,所以只能想办法通过.lnk文件获得文件的真实地址。

以下所有内容都来自网络,博主仅做了参考与总结。

.lnk文件格式解析

此处对lnk文件组成做一个大概介绍主旨是帮助了解如何从link文件中提取需要的信息
一个lnk文件包括一下几个模块:

模块 备注
文件头(lnk file header) 一些重要的文件信息。
Shell Item Id List 段 可选结构,由文件头中offset 0x14位置处的值来决定,0 bit值为1时,表示该lnk文件包含该结构。
文件位置信息段(File location info) 可选结构,由文件头中offset 0x14位置处的值来决定,1 bit值为1时,表示该lnk文件包含该结构。
描述字符段(Description string) 可选结构,由文件头中offset 0x14位置处的值来决定,2 bit值为1时,表示该lnk文件包含该结构。
相对路径段(Relative path string) 可选结构,由文件头中offset 0x14位置处的值来决定,3 bit值为1时,表示该lnk文件包含该结构。
工作目录段(Working directory string) 可选结构,由文件头中offset 0x14位置处的值来决定,4 bit值为1时,表示该lnk文件包含该结构。
命令行段(Command line string) 可选结构,由文件头中offset 0x14位置处的值来决定,5 bit值为1时,表示该lnk文件包含该结构。
图标文件段(Icon filename string) 可选结构,由文件头中offset 0x14位置处的值来决定,6 bit值为1时,表示该lnk文件包含该结构。
附加信息段(Extra stuff) 里面的数据具体含义未知,有些里面会包含一些本机机器名称。

注意:
不是所有的模块都必须包含在内,但如果存在就要按上述的顺序排列。

以下我们详细了解需要用到的两个模块:
1. 文件头(lnk file header)

偏移 长度 类型 备注
0x00 1 dword 总是为0000004CH,相当于字符"L",用于标识是否是个有效的.lnk文件。
0x04 16 bytes GUID,标识.lnk的唯一标识符,不排除以后MS对该字段有所修改。
0x14 1 dword flags标志,用来标识.lnk文件中有哪些可选属性,也就是哪些节是可选的。
0x18 1 dword 目标文件属性(是否只读、隐藏、系统文件、加密、临时…)
0x1c 1 qword 文件创建时间
0x24 1 qword 文件修改时间
0x2c 1 qword 文件最后一次访问时间
0x34 1 dword 目标文件长度
0x38 1 dword 自定义图标个数
0x3c 1 dword 目标文件执行时窗口显示方式(1-正常显示,2-最小化,3-最大化)
0x40 1 dword 热键
0x44 2 dword 该字段未知,常为0

0x14处16进制数的含义:

Bit 所在位为1时表示
0 包含shell item id list节,通过修改文件过滤掉该节,不影响.lnk执行目标文件,但影响其它功能。
1 指向文件或文件夹,如果此位为0表示指向其他。
2 存在描述字符串
3 存在相对路径
4 存在工作路径
5 存在命令行参数
6 存在自定义图标

0x18处16进制数的含义:

Bit 所在位为1时表示
0 快捷方式所指目标文件有只读属性
1 快捷方式所指目标文件有隐藏属性
2 快捷方式所指目标文件是系统文件
3 快捷方式所指目标是卷标
4 快捷方式所指目标是文件夹
5 快捷方式所指目标文件上次存档后被改变过
6 快捷方式所指目标文件被加密
7 快捷方式所指目标文件属性为一般
8 快捷方式所指目标文件为临时
9 快捷方式所指目标文件为稀疏文件(sparse file)
10 快捷方式所指目标文件有重分析点数据(reparse point)
11 快捷方式所指目标文件被压缩
12 快捷方式所指目标文件脱机

2. 文件位置信息段(File location info)

偏移 长度 类型 备注
0x00 1 dword 该节总长度,该值的修改,会影响.lnk文件执行或命令参数混乱失效。
0x04 1 dword 固定为0x1c,指明为该节长度,该值可任意修改。
0x08 1 dword flags标志,指示文件在哪些卷有效,例如是本地卷标还是网络卷标。
0x0c 1 dword 固定为0x1c,本地卷信息表偏移。
0x10 1 dword 指明目标文件路径相对本节头部的偏移,但.lnk文件执行目标程序并不依赖于该值指向的路径是否正确。
0x14 1 dword 网络卷信息表偏移,如果flags标记含有网络卷的话,否则为0。
0x18 1 dword 剩余偏移路径,一般都指向本节的末尾,也就是节总长度减1的值,当该值指向的位置为0时(大多数情况下都是0),该值可任意修改。

0x08偏移flags 具体含义:

  • 如果目标文件是本地文件,那么文件名称 = 本地路径信息+剩余偏移路径
  • 如果目标文件是网络文件,那么文件名称 = 网络卷中共享名称+剩余偏移路径

所以,File location info节之后的数据是,本地卷信息表,及网络卷信息表。
1. 本地卷信息表结构

偏移 长度 类型 备注
0x00 1 dword 本地卷信息表的长度,该值可任意修改。
0x04 1 dword 卷类型,该值可以任意修改。
0x08 1 dword 标识卷序列号,2byte一组,该值可任意修改。
0x0c 1 dword 卷名称的偏移,固定为0x10,该值可以任意修改。
0x10 可变长度 卷名称,本地路径信息,其大小由该表总长度决定。

2. 网络卷信息表结构

扫描二维码关注公众号,回复: 170713 查看本文章
偏移 长度 类型 备注
0x00 1 dword 网络卷信息表的长度,该值可任意修改。
0x04 1 dword 固定为0x2
0x08 1 dword 固定为0x14
0x0c 1 dword 固定为0
0x10 可变长度 固定为0x20000
0x10 可变长度 网络共享名

注意:
八个比特(Bit)称为一个字节(Byte),两个字节称为一个字(Word),两个字称为一个双字(Dword),两个双字称为一个四字(Qword)。

代码

private void parseLink(File f) throws FileNotFoundException, IOException {
    FileInputStream fin = new FileInputStream(f); 
    byte[] link = new byte[(int)f.length()]; 
    //读取文件中的内容到link[]数组
    fin.read(link);    
    fin.close(); 

    // 判断当前文件是否为快捷方式
    if(!isLnkFile(link)){
        return;
    }
    // 获得flags信息
    byte flags = link[0x14]; 

    int shell_len = 0;
    // 0000 0000 xxxx xxxx & 0000 0000 0000 0001(判断是否包含shell item id list段)
    if((flags & 0x1) > 0) { 
        // 如果存在,则获取shell item id list段的总长度,加2是为了将link[0x4c]本身的长度计算在内
        shell_len = bytes2short(link,0x4c) + 2; 
    } 
    // 获得文件位置信息段的开始位置=shell item id list段的开始位置+shell item id list段的总长度
    int file_start = 0x4c + shell_len; 
    // 获取本地路径信息的偏移
    int local_sys_off = link[file_start + 0x10] + file_start;
    String real_file = getNullDelimitedString(link, local_sys_off);
    System.out.println(real_file);
} 

private boolean isLnkFile(byte[] link) {
    if (link[0x00]== 0x4c) {// 76,L,0x4c代表lnk文件格式
        return true;
    }
    return false;
}

/**
 * 将两个字节转换为short<br>
 * 注意,因为仅限英特尔操作系统,所以这是小端字节<br>
 */
private int bytes2short(byte[] bytes, int off) {
    return bytes[off] | (bytes[off + 1] << 8);
}

/**
 * 获得从偏移位置off到以‘0’为结尾分割字符串
 * @param bytes 源数组
 * @param off 偏移位置
 * @return 字符串
 */
private String getNullDelimitedString(byte[] bytes, int off) {
    int len = 0;
    // 计算字符串占用数组的真实长度
    while (true) {
        if (bytes[off + len] == 0) {
            break;
        }
        len++;
    }
    byte[] results = new byte[len];
    for (int i = off, j = 0; i < off + len; i++, j++) {
        results[j] = bytes[i];
    }
    try {
        // 因为我是中文系统,所以设置了字符集GBK,否则中文路径会出现乱码
        return new String(bytes, off, len, "GBK");
    } catch (UnsupportedEncodingException e) {
        e.printStackTrace();
    }
    return null;
}

后话

GIT上也有一些项目实现了解析.lnk文件的功能,比如:
1. mslinks: 将.lnk封装对象可以通过get方法拿到想要的属性,但中文路径有乱码,如果愿意可以尝试自行修改源码;
2. jshortcut:用到了JNI,需要编译jshortcut.dll文件,没有尝试过,貌似同样存在中文乱码问题;

参考
  1. Windows快捷方式文件格式解析-http://www.cnblogs.com/ahuo/archive/2006/12/01/579503.html
  2. .lnk文件格式解析-http://pope12389.iteye.com/blog/1333585

猜你喜欢

转载自blog.csdn.net/zhuiyucanxin/article/details/79551708