背景与问题描述 某T品牌路由器使用了定制化的xz压缩格式存储固件,笔者经过研究后,最终实现了固件的提取方法,主要修改包括:
文件头magic值从标准的”\xfd7zXZ”修改为品牌字符串
修改了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 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 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) { ... 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