forked from openwrt/openwrt
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
lantiq: fritz_cal_extract with reverse option for AVM FritzBox 7430
This implementation of fritz_cal_extract can also retrieve firmware data stored in reverse byte order, as found in the AVM 7430 device. This is done by intermediate storage in a buffer presumably large enough to hold the complete data set. Currently, this buffer size is 128kB + 1kB (some extra space for skipped data). In the usual case of "forward" data, this implementation should behave like the original implementation in all common cases. limit [-l] will determine the amount of data read and size of buffer allocated. However, if you are reading reversed data or didn't set a limit, the buffer may be too small to hold all data. In this case, you can choose a higher limit [-l] to enforce a sufficient buffer size. Signed-off-by: Dustin Gathmann <[email protected]>
- Loading branch information
1 parent
5eb8b77
commit 05b3eef
Showing
2 changed files
with
203 additions
and
128 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,6 +8,9 @@ | |
* that is Not copyrighted -- provided to the public domain | ||
* Version 1.4 11 December 2005 Mark Adler | ||
* | ||
* Modifications to also handle calibration data in reversed byte order | ||
* (c) 2024 by <[email protected]>. | ||
* | ||
* This program is free software; you can redistribute it and/or modify | ||
* it under the terms of the GNU General Public License as published by | ||
* the Free Software Foundation; either version 2 of the License, or | ||
|
@@ -28,106 +31,126 @@ | |
#include <assert.h> | ||
#include <unistd.h> | ||
#include <stdint.h> | ||
#include <stdbool.h> | ||
#include <stdlib.h> | ||
#include <endian.h> | ||
#include <errno.h> | ||
#include "zlib.h" | ||
|
||
#define CHUNK 1024 | ||
#define DEFAULT_BUFFERSIZE (129 * 1024) | ||
|
||
#define MIN(a,b) (((a)<(b))?(a):(b)) | ||
|
||
static inline size_t special_min(size_t a, size_t b) | ||
/* Reverse byte order in data buffer. | ||
* 'top' is position of last valid data byte = (datasize - 1) | ||
*/ | ||
static void buffer_reverse(unsigned char *data, unsigned int top) | ||
{ | ||
return a == 0 ? b : (a < b ? a : b); | ||
register unsigned char swapbyte; | ||
const unsigned int center = top / 2; | ||
|
||
for (unsigned int bottom = 0; bottom < center; ++bottom, --top) { | ||
swapbyte = data[bottom]; | ||
data[bottom] = data[top]; | ||
data[top] = swapbyte; | ||
} | ||
} | ||
|
||
/* Decompress from file source to file dest until stream ends or EOF. | ||
inf() returns Z_OK on success, Z_MEM_ERROR if memory could not be | ||
allocated for processing, Z_DATA_ERROR if the deflate data is | ||
invalid or incomplete, Z_VERSION_ERROR if the version of zlib.h and | ||
the version of the library linked do not match, or Z_ERRNO if there | ||
is an error reading or writing the files. */ | ||
static int inf(FILE *source, FILE *dest, size_t limit, size_t skip) | ||
/* Decompress from file source to data buffer until stream ends | ||
* or *limit bytes have been written to buffer. | ||
* | ||
* On call, 'limit' must reference a variable containing the intended | ||
* number of bytes to retrieve (must be <= allocated buffer size). | ||
* | ||
* Return values (success): | ||
* Z_END_STREAM if complete data was retrieved (*limit == size of complete data), | ||
* or Z_OK if data was retrieved up to limit (*limit == original value). | ||
* | ||
* Return values (failure): | ||
* Z_MEM_ERROR if memory could not be allocated for processing, | ||
* Z_DATA_ERROR if the deflate data is invalid or incomplete, | ||
* Z_VERSION_ERROR if the version of zlib.h and the version of the | ||
* library linked do not match, or | ||
* Z_ERRNO if there is an error reading or writing the files. | ||
*/ | ||
static int inflate_to_buffer(FILE *source, unsigned char *buf, size_t *limit) | ||
{ | ||
int ret; | ||
size_t have; | ||
z_stream strm; | ||
unsigned char in[CHUNK]; | ||
unsigned char out[CHUNK]; | ||
|
||
/* allocate inflate state */ | ||
strm.zalloc = Z_NULL; | ||
strm.zfree = Z_NULL; | ||
strm.opaque = Z_NULL; | ||
strm.avail_in = 0; | ||
strm.next_in = Z_NULL; | ||
ret = inflateInit(&strm); | ||
if (ret != Z_OK) | ||
return ret; | ||
|
||
/* decompress until deflate stream ends or end of file */ | ||
do { | ||
strm.avail_in = fread(in, 1, CHUNK, source); | ||
if (ferror(source)) { | ||
(void)inflateEnd(&strm); | ||
return Z_ERRNO; | ||
} | ||
if (strm.avail_in == 0) | ||
break; | ||
strm.next_in = in; | ||
|
||
/* run inflate() on input until output buffer not full */ | ||
do { | ||
strm.avail_out = CHUNK; | ||
strm.next_out = out; | ||
ret = inflate(&strm, Z_NO_FLUSH); | ||
assert(ret != Z_STREAM_ERROR); /* state not clobbered */ | ||
switch (ret) { | ||
case Z_NEED_DICT: | ||
ret = Z_DATA_ERROR; /* and fall through */ | ||
case Z_DATA_ERROR: | ||
case Z_MEM_ERROR: | ||
(void)inflateEnd(&strm); | ||
return ret; | ||
} | ||
have = special_min(limit, CHUNK - strm.avail_out) - skip; | ||
if (fwrite(&out[skip], have, 1, dest) != 1 || ferror(dest)) { | ||
(void)inflateEnd(&strm); | ||
return Z_ERRNO; | ||
} | ||
skip = 0; | ||
limit -= have; | ||
} while (strm.avail_out == 0 && limit > 0); | ||
|
||
/* done when inflate() says it's done */ | ||
} while (ret != Z_STREAM_END && limit > 0); | ||
|
||
/* clean up and return */ | ||
(void)inflateEnd(&strm); | ||
return (limit == 0 ? Z_OK : (ret == Z_STREAM_END ? Z_OK : Z_DATA_ERROR)); | ||
int ret; | ||
z_stream strm; | ||
unsigned char in[CHUNK]; | ||
|
||
/* allocate inflate state */ | ||
strm.zalloc = Z_NULL; | ||
strm.zfree = Z_NULL; | ||
strm.opaque = Z_NULL; | ||
strm.avail_in = 0; | ||
strm.next_in = Z_NULL; | ||
ret = inflateInit(&strm); | ||
if (ret != Z_OK) | ||
return ret; | ||
|
||
/* set data buffer as stream output */ | ||
strm.avail_out = *limit; | ||
strm.next_out = buf; | ||
|
||
/* decompress until deflate stream ends or end of file */ | ||
do { | ||
strm.avail_in = fread(in, 1, CHUNK, source); | ||
if (ferror(source)) { | ||
(void)inflateEnd(&strm); | ||
return Z_ERRNO; | ||
} | ||
if (strm.avail_in == 0) | ||
break; | ||
strm.next_in = in; | ||
|
||
/* run inflate(), fill data buffer with all available output */ | ||
ret = inflate(&strm, Z_FINISH); | ||
assert(ret != Z_STREAM_ERROR); /* state not clobbered */ | ||
|
||
switch (ret) { | ||
case Z_NEED_DICT: | ||
ret = Z_DATA_ERROR; /* and fall through */ | ||
case Z_DATA_ERROR: | ||
case Z_MEM_ERROR: | ||
(void)inflateEnd(&strm); | ||
return ret; | ||
} | ||
/* done when inflate() says it's done or limit reached */ | ||
} while (ret != Z_STREAM_END && strm.avail_out > 0); | ||
|
||
/* set limit to end of retrieved data */ | ||
assert(strm.total_out <= *limit); | ||
*limit = strm.total_out; | ||
|
||
/* clean up and return */ | ||
(void)inflateEnd(&strm); | ||
return (ret == Z_STREAM_END ? Z_STREAM_END : (strm.avail_out == 0 ? Z_OK : Z_DATA_ERROR)); | ||
} | ||
|
||
/* report a zlib or i/o error */ | ||
static void zerr(int ret) | ||
{ | ||
switch (ret) { | ||
case Z_ERRNO: | ||
if (ferror(stdin)) | ||
fputs("error reading stdin\n", stderr); | ||
if (ferror(stdout)) | ||
fputs("error writing stdout\n", stderr); | ||
break; | ||
case Z_STREAM_ERROR: | ||
fputs("invalid compression level\n", stderr); | ||
break; | ||
case Z_DATA_ERROR: | ||
fputs("invalid or incomplete deflate data\n", stderr); | ||
break; | ||
case Z_MEM_ERROR: | ||
fputs("out of memory\n", stderr); | ||
break; | ||
case Z_VERSION_ERROR: | ||
fputs("zlib version mismatch!\n", stderr); | ||
} | ||
switch (ret) { | ||
case Z_ERRNO: | ||
if (ferror(stdin)) | ||
fputs("error reading stdin\n", stderr); | ||
if (ferror(stdout)) | ||
fputs("error writing stdout\n", stderr); | ||
break; | ||
case Z_STREAM_ERROR: | ||
fputs("invalid compression level\n", stderr); | ||
break; | ||
case Z_DATA_ERROR: | ||
fputs("invalid or incomplete deflate data\n", stderr); | ||
break; | ||
case Z_MEM_ERROR: | ||
fputs("out of memory\n", stderr); | ||
break; | ||
case Z_VERSION_ERROR: | ||
fputs("zlib version mismatch!\n", stderr); | ||
} | ||
} | ||
|
||
static unsigned int get_num(char *str) | ||
|
@@ -140,7 +163,8 @@ static unsigned int get_num(char *str) | |
|
||
static void usage(void) | ||
{ | ||
fprintf(stderr, "Usage: fritz_cal_extract [-s seek offset] [-i skip] [-o output file] [-l limit] [infile] -e entry_id\n" | ||
fprintf(stderr, "Usage: fritz_cal_extract -e entry_id [-s seek offset] [-l limit]\n" | ||
"\t[-r reverse extracted data] [-i skip n bytes] [-o output file] [infile]\n" | ||
"Finds and extracts zlib compressed calibration data in the EVA loader\n"); | ||
exit(EXIT_FAILURE); | ||
} | ||
|
@@ -154,53 +178,59 @@ struct cal_entry { | |
int main(int argc, char **argv) | ||
{ | ||
struct cal_entry cal = { .len = 0 }; | ||
unsigned char *buf = NULL; | ||
FILE *in = stdin; | ||
FILE *out = stdout; | ||
size_t datasize = DEFAULT_BUFFERSIZE; | ||
size_t limit = 0, skip = 0; | ||
int initial_offset = 0; | ||
int entry = -1; | ||
bool reversed = false, limit_was_set = true; | ||
int ret; | ||
int opt; | ||
|
||
while ((opt = getopt(argc, argv, "s:e:o:l:i:")) != -1) { | ||
while ((opt = getopt(argc, argv, "s:e:o:l:i:r")) != -1) { | ||
switch (opt) { | ||
case 's': | ||
initial_offset = (int)get_num(optarg); | ||
if (errno) { | ||
perror("Failed to parse seek offset"); | ||
goto out_bad; | ||
} | ||
break; | ||
case 'e': | ||
entry = (int) htobe16(get_num(optarg)); | ||
if (errno) { | ||
perror("Failed to entry id"); | ||
goto out_bad; | ||
} | ||
break; | ||
case 'o': | ||
out = fopen(optarg, "w"); | ||
if (!out) { | ||
perror("Failed to create output file"); | ||
goto out_bad; | ||
} | ||
break; | ||
case 'l': | ||
limit = (size_t)get_num(optarg); | ||
if (errno) { | ||
perror("Failed to parse limit"); | ||
goto out_bad; | ||
} | ||
break; | ||
case 'i': | ||
skip = (size_t)get_num(optarg); | ||
if (errno) { | ||
perror("Failed to parse skip"); | ||
goto out_bad; | ||
} | ||
break; | ||
default: /* '?' */ | ||
usage(); | ||
case 's': | ||
initial_offset = (int)get_num(optarg); | ||
if (errno) { | ||
perror("Failed to parse seek offset"); | ||
goto out_bad; | ||
} | ||
break; | ||
case 'e': | ||
entry = (int) htobe16(get_num(optarg)); | ||
if (errno) { | ||
perror("Failed to entry id"); | ||
goto out_bad; | ||
} | ||
break; | ||
case 'o': | ||
out = fopen(optarg, "w"); | ||
if (!out) { | ||
perror("Failed to create output file"); | ||
goto out_bad; | ||
} | ||
break; | ||
case 'l': | ||
limit = (size_t)get_num(optarg); | ||
if (errno) { | ||
perror("Failed to parse limit"); | ||
goto out_bad; | ||
} | ||
break; | ||
case 'i': | ||
skip = (size_t)get_num(optarg); | ||
if (errno) { | ||
perror("Failed to parse skip"); | ||
goto out_bad; | ||
} | ||
break; | ||
case 'r': | ||
reversed = true; | ||
break; | ||
default: /* '?' */ | ||
usage(); | ||
} | ||
} | ||
|
||
|
@@ -243,9 +273,50 @@ int main(int argc, char **argv) | |
goto out_bad; | ||
} | ||
|
||
ret = inf(in, out, limit, skip); | ||
if (ret == Z_OK) | ||
goto out; | ||
/* Set boundaries. Only keep default datasize if we need complete data | ||
* for reversal and didn't set a higher limit. */ | ||
if (!limit) { | ||
limit_was_set = false; | ||
limit = datasize - skip; | ||
} | ||
datasize = (reversed && datasize >= limit + skip) ? datasize : (limit + skip); | ||
|
||
/* Create data buffer. */ | ||
buf = malloc(datasize); | ||
assert(buf != NULL); | ||
|
||
ret = inflate_to_buffer(in, buf, &datasize); | ||
|
||
if ((reversed || !limit_was_set) && ret != Z_STREAM_END) { /* didn't read to stream end */ | ||
fprintf(stderr, "Failed: Data exceeds buffer size of %u. Refusing to reverse" | ||
" or store incomplete data." | ||
" Use a higher limit [-l] to increase buffer size.\n", | ||
(unsigned int) datasize); | ||
goto out_bad; | ||
} | ||
|
||
ret = (ret == Z_STREAM_END) ? Z_OK : ret; /* normalize return value */ | ||
if (ret != Z_OK) { | ||
zerr(ret); | ||
goto out_bad; | ||
} | ||
|
||
if (reversed) | ||
buffer_reverse(buf, datasize - 1); | ||
|
||
if (datasize <= skip) { | ||
fprintf(stderr, "Failed to skip %u bytes, total data size is %u!\n", | ||
(unsigned int)skip, (unsigned int)datasize); | ||
goto out_bad; | ||
} | ||
|
||
limit = MIN(limit, datasize - skip); | ||
if (fwrite(&buf[skip], limit, 1, out) != 1 || ferror(out)) { | ||
fprintf(stderr, "Failed to write data buffer to output file"); | ||
goto out_bad; | ||
} | ||
|
||
goto out; | ||
|
||
zerr(ret); | ||
|
||
|
@@ -257,5 +328,6 @@ int main(int argc, char **argv) | |
fclose(in); | ||
if (out) | ||
fclose(out); | ||
free(buf); | ||
return ret; | ||
} |
Oops, something went wrong.