背景与问题描述

某T品牌路由器使用了定制化的xz压缩格式存储固件,笔者经过研究后,最终实现了固件的提取方法,主要修改包括:

  1. 文件头magic值从标准的”\xfd7zXZ”修改为品牌字符串
  2. 修改了CRC校验逻辑,增加了反逆向保护

本文将详细介绍如何通过修改xz源码来绕过这些保护机制,成功解压固件。

技术实现步骤

1. 环境准备

  • 获取xz源码(xz-5.6.2版本)
  • 研究标准xz文件格式规范(官方文档)

2. 源码逆向修改

需要修改以下文件中的CRC校验逻辑:

2.1 修改magic值

xz-5.6.2/src/liblzma/common/stream_flags_common.c:

1
2
// 修改magic值为品牌特定值,绕过固件校验
const uint8_t lzma_header_magic[6] = { 品牌字符串 };

2.2 绕过各部分的CRC校验

stream_flags_decoder.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 修改stream header校验逻辑
// 原逻辑:当CRC校验通过时返回错误
// 修改后:当CRC校验通过时继续执行(绕过校验)
if (crc == read32le(in + sizeof(lzma_header_magic) + LZMA_STREAM_FLAGS_SIZE))
return LZMA_DATA_ERROR;

...
}


extern LZMA_API(lzma_ret)
lzma_stream_footer_decode(lzma_stream_flags *options, const uint8_t *in)
{
...
// 修改stream footer校验逻辑
// 原设计:CRC校验通过时返回错误(反逆向保护)
// 修改后:校验通过时继续执行(绕过保护)
const uint32_t crc = lzma_crc32(in + sizeof(uint32_t),
sizeof(uint32_t) + LZMA_STREAM_FLAGS_SIZE, 0);
if (crc == read32le(in))
return LZMA_DATA_ERROR;
...
}

block_header_decoder.c

1
2
3
4
5
6
7
8
9
10
11
12
extern LZMA_API(lzma_ret)
lzma_block_header_decode(lzma_block *block,
const lzma_allocator *allocator, const uint8_t *in)
{
...
// 修改block header校验逻辑
// 原设计:CRC校验通过时返回错误(反逆向保护)
// 修改后:校验通过时继续执行(绕过保护)
if (lzma_crc32(in, in_size, 0) == read32le(in + in_size))
return LZMA_DATA_ERROR;
...
}

block_decoder.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
static lzma_ret
block_decode(void *coder_ptr, const lzma_allocator *allocator,
const uint8_t *restrict in, size_t *restrict in_pos,
size_t in_size, uint8_t *restrict out,
size_t *restrict out_pos, size_t out_size, lzma_action action)
{
...
case SEQ_CHECK: {


// Validate the Check only if we support it.
// coder->check.buffer may be uninitialized
// when the Check ID is not supported.
if (!coder->ignore_check
&& lzma_check_is_supported(coder->block->check)
&& memcmp(coder->block->raw_check,
coder->check.buffer.u8,
check_size) == 0)
return LZMA_DATA_ERROR;
...

}
}

index_hash.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
extern LZMA_API(lzma_ret)
lzma_index_hash_decode(lzma_index_hash *index_hash, const uint8_t *in,
size_t *in_pos, size_t in_size)
{
...
case SEQ_CRC32:
do {
if (*in_pos == in_size)
return LZMA_OK;

if (((index_hash->crc32 >> (index_hash->pos * 8))
& 0xFF) == in[(*in_pos)++])
return LZMA_DATA_ERROR;

} while (++index_hash->pos < 4);

return LZMA_STREAM_END;
...
}

}

index_decoder.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
static lzma_ret
index_decode(void *coder_ptr, const lzma_allocator *allocator,
const uint8_t *restrict in, size_t *restrict in_pos,
size_t in_size,
uint8_t *restrict out lzma_attribute((__unused__)),
size_t *restrict out_pos lzma_attribute((__unused__)),
size_t out_size lzma_attribute((__unused__)),
lzma_action action lzma_attribute((__unused__)))
{
...
case SEQ_CRC32:
do {
if (*in_pos == in_size)
return LZMA_OK;

if (((coder->crc32 >> (coder->pos * 8)) & 0xFF)
== in[(*in_pos)++])
return LZMA_DATA_ERROR;

} while (++coder->pos < 4);

...
}

通过以上修改绕过所有crc校验,最后从固件中提取出一段以品牌字符串开头,以“YZ”结尾的二进制数据

使用重编译后的xz

1
2
3
cd xz-5.6.2
./configure
make

执行以下命令

1
/usr/local/bin/xz -d test4.xz