diff --git a/lzssdec/Makefile b/lzssdec/Makefile new file mode 100644 index 0000000..1d0184a --- /dev/null +++ b/lzssdec/Makefile @@ -0,0 +1,9 @@ +CXXFLAGS = -Wall + +.PHONY: all clean + +all: lzssdec + +clean: + -rm -f lzssdec + -rm -f *~ diff --git a/lzssdec/README.md b/lzssdec/README.md new file mode 100644 index 0000000..f0033f0 --- /dev/null +++ b/lzssdec/README.md @@ -0,0 +1,35 @@ +# LZSS Decompress + +[LZSS (Lempel–Ziv–Storer–Szymanski)](https://en.wikipedia.org/wiki/Lempel–Ziv–Storer–Szymanski) is a compression algorithm used to compress the Apple iOS kernelcache. +Apples provides their LZSS decompression implementation as [open source code](https://opensource.apple.com/source/BootX/BootX-59/bootx.tproj/sl.subproj/lzss.c). + +`lzzdec` is a tool created by Willem Hengeveld used to decompress LZSS-packed files. +Originally downloaded from [here](http://nah6.com/~itsme/cvs-xdadevtools/iphone/tools/lzssdec.cpp) (there are other links available in the Internet), it's referred by [NowSecure's guide to reversing the iOS kernel](https://www.nowsecure.com/blog/2014/04/14/ios-kernel-reversing-step-by-step/). + +A raw kernelcache file inside an IPSW file (such as `kernelcache.release.n41`) is encrypted for iOS <= 9 and compressed with LZSS for all iOS versions. +A (decrypted) kernelcache dump consists of a header and the actual LZSS-compressed kernelcache. +In order to use `lzssdec` you need to find the offset of the LZSS-compressed part in the kernelcache dump. +We created a custom Python script for that in `../../bin/get_lzss_section_offset.py`, by taking into account that the compressed part starts with the Mach-O magic header word `0xfeedface` or `0xfeedfacf`. + +You can build and run `lzssdec` on macOS and on Linux. + +You build `lzssdec` using + +``` +make +``` + +You run `lzssdec` by passing it the offset to the LZSS-compressed part, as provided by the `../../bin/get_lzss_section_offset.py` script and the (decrypted) kernelcache dump as standard input. +For example: + +``` +$ ../../bin/get_lzss_section_offset.py ~/Projects/store/out/iPhone5,1_9.3_13E237/kernelcache.decrypted +448 +$ ./lzssdec -o 448 < ~/Projects/store/out/iPhone5,1_9.3_13E237/kernelcache.decrypted > kernelcache.mach.arm +$ file kernelcache.mach.arm +kernelcache.mach.arm: Mach-O armv7s executable, flags: +``` + +iExtractor runs `lzssdec` as part of the `bin/decrypt_kernel` and `scripts/decrypt_kernel` scripts. + +[Joker](http://newosxbook.com/tools/joker.html) is also able to decompress kernelcache dumps but only for 64bit kernels. diff --git a/lzssdec/lzssdec.cpp b/lzssdec/lzssdec.cpp new file mode 100644 index 0000000..000e175 --- /dev/null +++ b/lzssdec/lzssdec.cpp @@ -0,0 +1,250 @@ +// (C)2009 Willem Hengeveld itsme@xs4all.nl +#include +#include +#include +#include +#include +#include + +// streaming version of the lzss algorithm, as defined in BootX-75/bootx.tproj/sl.subproj/lzss.c +// you can use lzssdec in a filter, like: +// +// cat file.lzss | lzssdec > file.decompressed +// +static int g_debug= 0; + +class lzssdecompress +{ + enum { COPYFROMDICT, EXPECTINGFLAG, PROCESSFLAGBIT, EXPECTING2NDBYTE }; + int _state; + uint8_t _flags; + int _bitnr; + uint8_t *_src, *_srcend; + uint8_t *_dst, *_dstend; + uint8_t _firstbyte; + + uint8_t *_dict; + + int _dictsize; + int _maxmatch; + int _copythreshold; + + int _dictptr; + + int _copyptr; + int _copycount; + + int _inputoffset; + int _outputoffset; +public: + lzssdecompress() + { + _maxmatch= 18; // 4 bit size + threshold + _dictsize= 4096; // 12 bit size + _copythreshold= 3; // 0 == copy 3 bytes + _dict= new uint8_t[_dictsize+_maxmatch-1]; + + reset(); + } + ~lzssdecompress() + { + delete[] _dict; + _dict= 0; _dictsize= 0; + } + void reset() + { + _state=EXPECTINGFLAG; + _flags= 0; _bitnr= 0; + _src=_srcend=_dst=_dstend=0; + memset(_dict, ' ', _dictsize+_maxmatch-1); + _dictptr= _dictsize-_maxmatch; + _inputoffset= 0; + _outputoffset= 0; + _firstbyte= 0; + _copyptr= 0; + _copycount= 0; + } + void decompress(uint8_t *dst, uint32_t dstlen, uint32_t *pdstused, uint8_t *src, uint32_t srclen, uint32_t *psrcused) + { + _src= src; _srcend= src+srclen; + _dst= dst; _dstend= dst+dstlen; + + while (_src<_srcend && _dst<_dstend) + { + switch(_state) + { + case EXPECTINGFLAG: + if (g_debug) fprintf(stderr, "%08x,%08x: flag: %02x\n", _inputoffset, _outputoffset, *_src); + _flags= *_src++; + _inputoffset++; + _bitnr= 0; + _state= PROCESSFLAGBIT; + break; + case PROCESSFLAGBIT: + if (_flags&1) { + if (g_debug) fprintf(stderr, "%08x,%08x: bit%d: %03x copybyte %02x\n", _inputoffset, _outputoffset, _bitnr, _dictptr, *_src); + addtodict(*_dst++ = *_src++); + _inputoffset++; + _outputoffset++; + nextflagbit(); + } + else { + _firstbyte= *_src++; + _inputoffset++; + _state= EXPECTING2NDBYTE; + } + break; + case EXPECTING2NDBYTE: + { + uint8_t secondbyte= *_src++; + _inputoffset++; + setcounter(_firstbyte, secondbyte); + if (g_debug) fprintf(stderr, "%08x,%08x: bit%d: %03x %02x %02x : copy %d bytes from %03x", _inputoffset-2, _outputoffset, _bitnr, _dictptr, _firstbyte, secondbyte, _copycount, _copyptr); + if (g_debug) dumpcopydata(); + _state= COPYFROMDICT; + } + break; + case COPYFROMDICT: + copyfromdict(); + break; + } + } + if (g_debug) fprintf(stderr, "decompress state= %d, copy: 0x%x, 0x%x\n", _state, _copyptr, _copycount); + if (pdstused) *pdstused= _dst-dst; + if (psrcused) *psrcused= _src-src; + } + void flush(uint8_t *dst, uint32_t dstlen, uint32_t *pdstused) + { + if (g_debug) fprintf(stderr, "flash before state= %d, copy: 0x%x, 0x%x\n", _state, _copyptr, _copycount); + _src= _srcend= NULL; + _dst= dst; _dstend= dst+dstlen; + + if (_state==COPYFROMDICT) + copyfromdict(); + + if (pdstused) *pdstused= _dst-dst; + if (g_debug) fprintf(stderr, "flash after state= %d, copy: 0x%x, 0x%x\n", _state, _copyptr, _copycount); + } + void copyfromdict() + { + while (_dst<_dstend && _copycount) + { + addtodict(*_dst++ = _dict[_copyptr++]); + _outputoffset++; + _copycount--; + _copyptr= _copyptr&(_dictsize-1); + } + if (_copycount==0) + nextflagbit(); + } + void dumpcopydata() + { + // note: we are printing incorrect data, if _copyptr == _dictptr-1 + for (int i=0 ; i<_copycount ; i++) + fprintf(stderr, " %02x", _dict[(_copyptr+i)&(_dictsize-1)]); + fprintf(stderr, "\n"); + } + void addtodict(uint8_t c) + { + _dict[_dictptr++]= c; + _dictptr = _dictptr&(_dictsize-1); + } + void nextflagbit() + { + _bitnr++; + _flags>>=1; + _state = _bitnr==8 ? EXPECTINGFLAG : PROCESSFLAGBIT; + } + void setcounter(uint8_t first, uint8_t second) + { + _copyptr= first | ((second&0xf0)<<4); + _copycount= _copythreshold + (second&0xf); + } +}; + +void usage(int argc,char**argv) +{ + char *name = NULL; + name = strrchr(argv[0], '/'); + fprintf(stderr, "Usage: %s [-d] [-o OFFSET] \n",(name ? name + 1: argv[0])); +} +int main(int argc,char**argv) +{ +// _setmode(fileno(stdin),O_BINARY); +// _setmode(fileno(stdout),O_BINARY); + +#define HANDLEULOPTION(var, type) (argv[i][2] ? var= (type)strtoul(argv[i]+2, 0, 0) : i+1 bytes + while (skipbytes && !feof(stdin)) { + int nr= fread(ibuf, 1, std::min(skipbytes,(uint32_t)CHUNK), stdin); + skipbytes -= nr; + } + + while (!feof(stdin)) + { + size_t nr= fread(ibuf, 1, CHUNK, stdin); + if (nr==0) { + perror("read"); + return 1; + } + if (nr==0) + break; + + size_t srcp= 0; + while (srcp 0x%x\n", srcused, dstused); + } + } + if (g_debug) fprintf(stderr, "done reading\n"); + uint32_t dstused; + lzss.flush(obuf, CHUNK, &dstused); + size_t nw= fwrite(obuf, 1, dstused, stdout); + if (nw