2024年1月28日发(作者:)

.lnk文件格式解析

.lnk文件格式解析

由于.lnk文件提供丰富的调用方式,因此在研究该文件类型格式的基础的可以发现有很多字段可以被恶意利用。下面是一个.lnk文件格式的通用结构,也就

说.lnk文件是由这样不同的节组成的,但这些节并不是全都必须存在。

.lnk 文件格式

+---------------------------+

| lnk file header |

+---------------------------+ >------.

| Shell Item Id List | |

+---------------------------+ |

| File location info | |

+---------------------------+ |

| Description string | |

+---------------------------+ ---

| Relative path string | 这几个节不是每一个都必须存在,如果存在就要按这样的顺序关系排列。

+---------------------------+ ---

| Working directory string | |

+---------------------------+ |

| Command line string | |

+---------------------------+ |

| Icon filename string | |

+---------------------------+ >------.

| Extra stuff |

+---------------------------+

1 首先是文件头lnk file header结构,这里记录了比较重要的文件信息,无论是恶意代码的感染还是用于检测,都需要对该结构有所了解,下面是文件头

结构参考数据。

Offset Size/Type Description

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处的值含义:

bit 该bit置1

0 包含shell item id list节,通过修改文件过滤掉该节,不影响.lnk执行目标文件,但影响其它功能。

1 指向文件或文件夹,如果此位为0表示指向其他。

2 存在描述字符串

3 存在相对路径

4 存在工作路径

5 存在命令行参数

6 存在自定义图标 *--> 该位置一般用于病毒判断是否感染目标文件的标志.

2 Shell Item Id List 节是个可选结构,由文件头中offset 0x14位置处的值来决定,0 bit值为1时,表示该lnk文件包含该结构。

如果存在该结构,偏移0x4c的位置的一个unsigned short int是Shell Item Id List结构的大小标识,后面紧跟一个SHITEMID结构,该结构体定义如下

typedef struct _SHITEMID

{

unsigned short int cb;

unsigned char abID[0];

}SHITEMID,*LPSHITEMID;

cb标识一项SHITEMID结构大小,abID是可变结构,存储数据具体数据,里面的含义有些未知,但第0项里面的数据是不能修改的,否则.lnk文件无法运行。

Shell Item Id List 结构

/- +-------------------------------+

| |size (该节总长度) |------> 该项如果修改,会导致.lnk无法运行,

如果含有命令参数,也会混乱。

| +-------------------------------+

| |SHITEMID[0] |------> 该项所有值均固定,unsigned char

data[22] = {

V +-------------------------------+ 0x14, 0x00, 0x1F, 0x50,

0xE0, 0x4F, 0xD0, 0x20, 0xEA, 0x3A, 0x69, 0x10, 0xA2, 0xD8, 0x08,

size |SHITEMID[1] |------ 0x00, 0x2B, 0x30, 0x30, 0x9D, 0x19,

0x00};

A +-------------------------------+ | 也就是cb=0x0014,修改任意值都会导致.lnk无法运行。

| |... | |

| | | .------> 该项cb的值一般固定为0x2f(也可能是别的值),不可修改。abID的值为指向目标程序所在盘符的字符串

| | | 如"C:",该段空间数据可以任意修改。

| | |------

| +-------------------------------+ | -----> 修改会影响.lnk文件执行。

| |SHITEMID[n] |------/

- +-------------------------------+

3 File location info,一般指文件信息节,是个可选结构,由文件头中offset 0x14位置处的值来决定,1 bit值为1时,表示该lnk文件包含该结构。

Offset Size/Type Description

0x00 1dword 该节总长度,该值的修改,会影响.lnk文件执行或命令参数混乱失效。

0x04 1dword 固定为0x1c,指明为该节长度,该值可任意修改。

0x08 1dword flags标志,指示文件在哪些卷有效,例如是本地

卷标还是网络卷标。

0x0c 1dword 固定为0x1c,本地卷信息表偏移。

0x10 1dword 指明目标文件路径相对本节头部的偏移,但.lnk文件执行目标程序并不依赖于该值指向的路径是否正确。

0x14 1dword 网络卷信息表偏移,如果flags标记含有网络卷的话,否则为0.

0x18 1dword 剩余偏移路径,一般都指向本节的末尾,也就是节总长度减1的值,当该值指向的位置为0时(大多数情况下都是0),该值可任意修改。

flags 具体含义:

如果目标文件是本地文件,那么文件名称 = 本地路径信息+剩余偏移路径

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

接着File location info节的数据是,本地卷信息表,及网络卷信息表。

本地卷信息表结构如下:

Offset Size/Type Description

0x00 1dword 本地卷信息表的长度,该值可任意修改。

0x04 1dword 卷类型(网络盘,软盘,硬盘,可移动...后面有说明),该值可以任意修改。

0x08 1dword 标识卷序列号,2byte一组,该值可任意修改。

0x0c 1dword 卷名称的偏移,固定为0x10,该值可以任意修改。

0x10 可变长度 卷名称,其大小由该表总长度决定。

网络卷信息表结构如下:

Offset Size/Type Description

0x00 1dword 网络卷信息表的长度,该值可任意修改。

0x04 1dword 固定为0x2

0x08 1dword 固定为0x14

0x0c 1dword 固定为0

0x10 可变长度 固定为0x20000

0x10 可变长度 网络共享名

4 Description string 一般指描述字符节,是个可选结构,由文件头中offset 0x14位置处的值来决定,2 bit值为1时,表示该lnk文件包含该结构。

该节开始是个unsigned short int 表示该节的长度,不包含这2byte,由于该字符串的描述字符为unicode字符,所以取得该值后还得乘以2.

该字符也就是右键查看快捷方式信息中的“起始位置(S)”中的那个描述,该值修改不影响.lnk文件执行。

5 Relative path string 一般指相对路径节,是个可选结构,由文件头中offset 0x14位置处的值来决定,3 bit值为1时,表示该lnk文件包含该结构。

该节结构同上。

6 Working directory string 一般指工作路径节,是个可选结构,由文件头中offset 0x14位置处的值来决定,4 bit值为1时,表示该lnk文件包含该结构。

该节结构同上。

7 Command line string 一般指命令行节,是个可选结构,由文件头中offset 0x14位置处的值来决定,5 bit值为1时,表示该lnk文件包含该结构。

该节结构同上。

8 Icon filename string 一般指自定义图标节,是个可选结构,由文件头中offset 0x14位置处的值来决定,6 bit值为1时,表示该lnk文件包含该结构。

该节结构同上。

9 附加数据节,里面的数据具体含义未知,有些里面会包含一些本机机器名称。

以上就是对.lnk文件格式的简要分析,对.lnk进行感染最主要的就是偷换.lnk指向的目标文件路径,这点和映像劫持有些类似。同时保留原目标文件路径,

留在后面调用,以防感染后被人察觉到快捷方式失效,这样在恶意程序后台启动后,还会调用原目标文件。

import rayOutputStream;

import ;

import putStream;

import lFormat;

import Format;

public class LnkParser {

public LnkParser(File f) throws Exception {

parse(f);

}

private boolean is_dir;

public boolean isDirectory() {

return is_dir;

}

private String real_file;

public String getRealFilename() {

return real_file;

}

public void parse(File f) throws Exception {

// read the entire file into a byte buffer

FileInputStream fin = new FileInputStream(f);

ByteArrayOutputStream bout =

ByteArrayOutputStream();

byte[] buff = new byte[256];

while(true) {

int n = (buff);

if(n == -1) { break; }

(buff,0,n);

}

();

byte[] link = Array();

// get the flags byte

byte flags = link[0x14];

new

// get the file attributes byte

final int file_atts_offset = 0x18;

byte fileatts = link[file_atts_offset];

byte is_dir_mask = (byte)0x10;

if((fileatts & is_dir_mask) > 0) {

is_dir = true;

} else {

is_dir = false;

}

// if the shell settings are present, skip them

final int shell_offset = 0x4c;

int shell_len = 0;

if((flags & 0x1) > 0) {

// the plus 2 accounts for the length marker itself

shell_len = bytes2short(link,shell_offset) + 2;

}

// get to the file settings

int file_start = 0x4c + shell_len;

// get the local volume and local system values

int local_sys_off = link[file_start+0x10] + file_start;

real_file = getNullDelimitedString(link,local_sys_off);

p("real filename = " + real_file);

}

static String getNullDelimitedString(byte[] bytes, int off) {

int len = 0;

// count bytes until the null character (0)

while(true) {

if(bytes[off+len] == 0) {

break;

}

len++;

}

return new String(bytes,off,len);

}

// convert two bytes into a short

// note, this is little endian because it's for an

// Intel only OS.

static int bytes2short(byte[] bytes, int off) {

return bytes[off] | (bytes[off+1]<<8);

}

/*

static int norm(byte b) {

if(b < 0) {

b+=128;

}

return b;

}

static int bytes2int(byte[] arr, int off) {

int b1 = norm(arr[off]);

int b2 = norm(arr[off+1]);

int b3 = norm(arr[off+2]);

int b4 = norm(arr[off+3]);

int val = (

(b1 << 0) |

(b2 << 8 ) |

(b3 << 16) |

(b4 << 24)

);

//p("bytes2int: " + b1 + " " + b2 + " " + b3 + " " + b4);

return val;

}

static NumberFormat num_format = new DecimalFormat("

000;-000");

public static String padd(String str, int len) {

while(() < len) {

str = " " + str;

}

return str;

}

public static void pw(byte[] arr, int off) {

StringBuffer top = new StringBuffer();

StringBuffer mid = new StringBuffer();

StringBuffer bot = new StringBuffer();

("--");

(" ");

(" ");

for(int i=0; i<16; i++) {

int val = arr[off+i];

String str = tring(off+i);

(padd(str,5));

(padd(""+val,5));

if(val < 0) {

val += 128;

}

if(val >= ' ' && val <= '~') {

str = "" + (char)val;

} else {

str = tring(val);

}

str = padd(str,5);

(str);

if(i%4==3) {

(" ");

(" ");

(" ");

}

}

p(ng());

p(ng());

p(ng());

}

public static void pbits(byte bt) {

p("byte = " + bt + " " + tring(bt) + " " +

ryString(bt));

}*/

public static void p(String str) {

n(str);

}

}