1 /* p_vmlinz.cpp --
2 
3    This file is part of the UPX executable compressor.
4 
5    Copyright (C) 1996-2020 Markus Franz Xaver Johannes Oberhumer
6    Copyright (C) 1996-2020 Laszlo Molnar
7    All Rights Reserved.
8 
9    UPX and the UCL library are free software; you can redistribute them
10    and/or modify them under the terms of the GNU General Public License as
11    published by the Free Software Foundation; either version 2 of
12    the License, or (at your option) any later version.
13 
14    This program is distributed in the hope that it will be useful,
15    but WITHOUT ANY WARRANTY; without even the implied warranty of
16    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17    GNU General Public License for more details.
18 
19    You should have received a copy of the GNU General Public License
20    along with this program; see the file COPYING.
21    If not, write to the Free Software Foundation, Inc.,
22    59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23 
24    Markus F.X.J. Oberhumer              Laszlo Molnar
25    <markus@oberhumer.com>               <ezerotven+github@gmail.com>
26  */
27 
28 
29 #include "conf.h"
30 
31 #include "p_elf.h"
32 #include "file.h"
33 #include "filter.h"
34 #include "packer.h"
35 #include "p_vmlinz.h"
36 #include "linker.h"
37 #include <zlib.h>
38 
39 static const
40 #include "stub/i386-linux.kernel.vmlinuz.h"
41 
42 static const unsigned stack_offset_during_uncompression = 0x9000;
43 // add to "real mode pointer" in %esi; total 0x99000 is typical
44 
45 // from /usr/src/linux/arch/i386/boot/compressed/Makefile
46 static const unsigned zimage_offset = 0x1000;
47 static const unsigned bzimage_offset = 0x100000;
48 
49 
50 /*************************************************************************
51 //
52 **************************************************************************/
53 
PackVmlinuzI386(InputFile * f)54 PackVmlinuzI386::PackVmlinuzI386(InputFile *f) :
55     super(f), physical_start(0x100000), page_offset(0), config_physical_align(0)
56     , filter_len(0)
57 {
58     bele = &N_BELE_RTP::le_policy;
59     COMPILE_TIME_ASSERT(sizeof(boot_sect_t) == 0x250);
60 }
61 
62 
getCompressionMethods(int method,int level) const63 const int *PackVmlinuzI386::getCompressionMethods(int method, int level) const
64 {
65     return Packer::getDefaultCompressionMethods_le32(method, level);
66 }
67 
68 
getFilters() const69 const int *PackVmlinuzI386::getFilters() const
70 {
71     static const int filters[] = {
72         0x26, 0x24, 0x49, 0x46, 0x16, 0x13, 0x14, 0x11,
73         FT_ULTRA_BRUTE, 0x25, 0x15, 0x12,
74     FT_END };
75     return filters;
76 }
77 
getFilters() const78 const int *PackBvmlinuzI386::getFilters() const
79 {
80     // The destination buffer might be relocated at runtime.
81     static const int filters[] = {
82         0x49, 0x46,
83     FT_END };
84     return filters;
85 }
86 
getStrategy(Filter &)87 int PackVmlinuzI386::getStrategy(Filter &/*ft*/)
88 {
89     // If user specified the filter, then use it (-2==filter_strategy).
90     // Else try the first two filters, and pick the better (2==filter_strategy).
91     return (opt->no_filter ? -3 : ((opt->filter > 0) ? -2 : 2));
92 }
93 
94 
canPack()95 bool PackVmlinuzI386::canPack()
96 {
97     return readFileHeader() == getFormat();
98 }
99 
100 
101 /*************************************************************************
102 // common util routines
103 **************************************************************************/
104 
readFileHeader()105 int PackVmlinuzI386::readFileHeader()
106 {
107     setup_size = 0;
108 
109     fi->readx(&h, sizeof(h));
110     if (h.boot_flag != 0xAA55)
111         return 0;
112     const bool hdrs = (memcmp(h.hdrs, "HdrS", 4) == 0);
113 
114     setup_size = (1 + (h.setup_sects ? h.setup_sects : 4)) * 0x200;
115     if (setup_size <= 0 || setup_size >= file_size)
116         return 0;
117 
118     int format = UPX_F_VMLINUZ_i386;
119     unsigned sys_size = ALIGN_UP((unsigned) file_size, 16u) - setup_size;
120 
121     const unsigned char *p = (const unsigned char *) &h + 0x1e3;
122 
123     if (hdrs && memcmp(p, "\x0d\x0a\x07""ELKS", 7) == 0)
124     {
125         format = UPX_F_ELKS_8086;
126     }
127     else if (hdrs && (h.load_flags & 1) != 0)
128     {
129         format = UPX_F_BVMLINUZ_i386;
130     }
131 
132     if (0x204<=h.version) {
133         if ((16u * h.sys_size) != sys_size)
134             return 0;
135     }
136     else { // h.sys_size is only 2 bytes
137         if ((16u * (0xffff & h.sys_size)) != (~(~0u<<20) & sys_size))
138             return 0;
139     }
140 
141     // FIXME: add more checks for a valid kernel
142 
143     return format;
144 }
145 
146 
is_pow2(unsigned const x)147 static int is_pow2(unsigned const x)
148 {
149     return !(x & (x - 1));
150 }
151 
152 // read full kernel into obuf[], gzip-decompress into ibuf[],
153 // return decompressed size
decompressKernel()154 int PackVmlinuzI386::decompressKernel()
155 {
156     // read whole kernel image
157     obuf.alloc(file_size);
158     fi->seek(0, SEEK_SET);
159     fi->readx(obuf, file_size);
160 
161     {
162     const upx_byte *base = NULL;
163     unsigned relocated = 0;
164 
165     // See startup_32: in linux/arch/i386/boot/compressed/head.S
166     const upx_byte *p;
167     unsigned cpa_0 = 0;
168     unsigned cpa_1 = 0;
169     int j;
170     if (0x205<=h.version) {
171         cpa_0 = h.kernel_alignment;
172         cpa_1 = 0u - cpa_0;
173     } else
174     for ((p = &obuf[setup_size]), (j= 0); j < 0x200; ++j, ++p) {
175         if (0==memcmp("\x89\xeb\x81\xc3", p, 4)
176         &&  0==memcmp("\x81\xe3",      8+ p, 2)) {
177             // movl %ebp,%ebx
178             // addl $imm.w,%ebx
179             // andl $imm.w,%ebx
180             cpa_0 = 1+ get_te32( 4+ p);
181             cpa_1 =    get_te32(10+ p);
182             break;
183         }
184     }
185     for ((p = &obuf[setup_size]), (j= 0); j < 0x200; ++j, ++p) {
186         if (0==memcmp("\x8d\x83",    p, 2)  // leal d32(%ebx),%eax
187         &&  0==memcmp("\xff\xe0", 6+ p, 2)  // jmp *%eax
188         ) {
189             relocated = get_te32(2+ p);
190         }
191         if (0==memcmp("\xE8\x00\x00\x00\x00\x5D", p, 6)) {
192             // "call 1f; 1f: pop %ebp"  determines actual execution address.
193             // linux-2.6.21 (spring 2007) and later; upx stub needs work
194             // unless LOAD_PHYSICAL_ADDR is known.
195             // Allowed code is:  linux-2.6.23/arch/x86/head_32.S  2008-01-01
196             //      call 1f
197             //  1:  popl %ebp
198             //      subl $1b, %ebp  # 32-bit immediate
199             //      movl $LOAD_PHYSICAL_ADDR, %ebx
200             //
201             if (0==memcmp("\x81\xed", 6+  p, 2)      // subl $imm.w,%ebp
202             &&  0==memcmp("\xbb",     12+ p, 1) ) {  // movl $imm.w,%ebx
203                 physical_start = get_te32(13+ p);
204             } else
205             if (0==memcmp("\x81\xed", 6+  p, 2)  // subl $imm.w,%ebp
206             &&  is_pow2(cpa_0) && (0u-cpa_0)==cpa_1) {
207                 base = (5+ p) - get_te32(8+ p);
208                 config_physical_align = cpa_0;
209             }
210             else {
211                 throwCantPack("Unrecognized relocatable kernel");
212             }
213         }
214         // Find "ljmp $__BOOT_CS,$__PHYSICAL_START" if any.
215         if (0==memcmp("\xEA\x00\x00", p, 3) && 0==(0xf & p[3]) && 0==p[4]) {
216             /* whole megabyte < 16 MiB */
217             physical_start = get_te32(1+ p);
218             break;
219         }
220     }
221     if (base && relocated) {
222         p = base + relocated;
223         for (j = 0; j < 0x200; ++j, ++p) {
224             if (0==memcmp("\x01\x9c\x0b", p, 3)  // addl %ebx,d32(%ebx,%ecx)
225             ) {
226                 page_offset = 0u - get_te32(3+ p);
227             }
228             if (0==memcmp("\x89\xeb",    p, 2)  // movl %ebp,%ebx
229             &&  0==memcmp("\x81\xeb", 2+ p, 2)  // subl $imm32,%ebx
230             ) {
231                 physical_start = get_te32(4+ p);
232             }
233         }
234     }
235     }
236 
237     checkAlreadyPacked(obuf + setup_size, UPX_MIN(file_size - setup_size, (off_t)1024));
238 
239     int gzoff = setup_size;
240     if (0x208<=h.version) {
241         gzoff += h.payload_offset;
242     }
243     for (; gzoff < file_size; gzoff++)
244     {
245         // find gzip header (2 bytes magic + 1 byte method "deflated")
246         int off = find(obuf + gzoff, file_size - gzoff, "\x1F\x8B\x08", 3);
247         if (off < 0)
248             break;
249         gzoff += off;
250         const int gzlen = (h.version < 0x208) ? (file_size - gzoff) : h.payload_length;
251         if (gzlen < 256)
252             break;
253         // check gzip flag byte
254         unsigned char flags = obuf[gzoff + 3];
255         if ((flags & 0xe0) != 0)        // reserved bits set
256             continue;
257         //printf("found gzip header at offset %d\n", gzoff);
258 
259         // try to decompress
260         int klen;
261         int fd;
262         off_t fd_pos;
263         for (;;)
264         {
265             klen = -1;
266             fd_pos = -1;
267             // open
268             fi->seek(gzoff, SEEK_SET);
269             fd = dup(fi->getFd());
270             if (fd < 0)
271                 break;
272             gzFile zf = gzdopen(fd, "rb");
273             if (zf == NULL)
274                 break;
275             // estimate gzip-decompressed kernel size & alloc buffer
276             if (ibuf.getSize() == 0)
277                 ibuf.alloc(gzlen * 3);
278             // decompress
279             klen = gzread(zf, ibuf, ibuf.getSize());
280             fd_pos = lseek(fd, 0, SEEK_CUR);
281             gzclose(zf);
282             fd = -1;
283             if (klen != (int)ibuf.getSize())
284                 break;
285             // realloc and try again
286             unsigned s = ibuf.getSize();
287             ibuf.dealloc();
288             ibuf.alloc(3 * s / 2);
289         }
290         if (fd >= 0)
291             (void) close(fd);
292         if (klen <= 0)
293             continue;
294 
295         if (klen <= gzlen)
296             continue;
297 
298         if (0x208<=h.version && 0==memcmp("\177ELF", ibuf, 4)) {
299             // Full ELF in theory; for now, try to handle as .bin at physical_start.
300             // Check for PT_LOAD.p_paddr being ascending and adjacent.
301             Elf_LE32_Ehdr const *const ehdr = (Elf_LE32_Ehdr const *)(void const *)ibuf;
302             Elf_LE32_Phdr const *phdr = (Elf_LE32_Phdr const *)(ehdr->e_phoff + (char const *)ehdr);
303             Elf_LE32_Shdr const *shdr = (Elf_LE32_Shdr const *)(ehdr->e_shoff + (char const *)ehdr);
304             unsigned hi_paddr = 0, lo_paddr = 0;
305             unsigned delta_off = 0;
306             for (unsigned j=0; j < ehdr->e_phnum; ++j, ++phdr) {
307                 if (phdr->PT_LOAD==phdr->p_type) {
308                     unsigned step = (hi_paddr + phdr->p_align - 1) & ~(phdr->p_align - 1);
309                     if (0==hi_paddr) { // first PT_LOAD
310                         if (physical_start!=phdr->p_paddr) {
311                             return 0;
312                         }
313                         delta_off = phdr->p_paddr - phdr->p_offset;
314                         lo_paddr = phdr->p_paddr;
315                         hi_paddr = phdr->p_filesz + phdr->p_paddr;
316                     }
317                     else if (step==phdr->p_paddr
318                         && delta_off==(phdr->p_paddr - phdr->p_offset)) {
319                         hi_paddr = phdr->p_filesz + phdr->p_paddr;
320                     }
321                     else {
322                         return 0;  // Not equivalent to a .bin.  Too complex for now.
323                     }
324                 }
325             }
326             // FIXME: ascending order is only a convention; might need sorting.
327             for (unsigned j=1; j < ehdr->e_shnum; ++j) {
328                 if (shdr->SHT_PROGBITS==shdr->sh_type) { // SHT_REL might be intermixed
329                     if (shdr->SHF_EXECINSTR & shdr[j].sh_flags) {
330                         filter_len += shdr[j].sh_size;  // FIXME: include sh_addralign
331                     }
332                     else {
333                         break;
334                     }
335                 }
336             }
337             memmove(ibuf, (lo_paddr - delta_off) + ibuf, hi_paddr - lo_paddr);  // FIXME: set_size
338             // FIXME: .bss ?  Apparently handled by head.S
339         }
340 
341         if (opt->force > 0)
342             return klen;
343 
344         // some checks
345         if (fd_pos != file_size)
346         {
347             //printf("fd_pos: %ld, file_size: %ld\n", (long)fd_pos, (long)file_size);
348 
349             // linux-2.6.21.5/arch/i386/boot/compressed/vmlinux.lds
350             // puts .data.compressed ahead of .text, .rodata, etc;
351             // so piggy.o need not be last in bzImage.  Alas.
352             //throwCantPack("trailing bytes after kernel image; use option '-f' to force packing");
353         }
354 
355 
356         // see /usr/src/linux/arch/i386/kernel/head.S
357         // 2.4.x: [cli;] cld; mov $...,%eax
358         if (memcmp(ibuf,     "\xFC\xB8", 2) == 0) goto head_ok;
359         if (memcmp(ibuf, "\xFA\xFC\xB8", 3) == 0) goto head_ok;
360         // 2.6.21.5 CONFIG_PARAVIRT  mov %cs,%eax; test $3,%eax; jne ...;
361         if (memcmp(ibuf, "\x8c\xc8\xa9\x03\x00\x00\x00\x0f\x85", 9) == 0) goto head_ok;
362         if (memcmp(ibuf, "\x8c\xc8\xa8\x03\x0f\x85", 6) == 0) goto head_ok;
363         // 2.6.x: [cli;] cld; lgdt ...
364         if (memcmp(ibuf,     "\xFC\x0F\x01", 3) == 0) goto head_ok;
365         if (memcmp(ibuf, "\xFA\xFC\x0F\x01", 4) == 0) goto head_ok;
366         // 2.6.x+grsecurity+strongswan+openwall+trustix: ljmp $0x10,...
367         if (ibuf[0] == 0xEA && memcmp(ibuf+5, "\x10\x00", 2) == 0) goto head_ok;
368         // x86_64 2.6.x
369         if (0xB8==ibuf[0]  // mov $...,%eax
370         &&  0x8E==ibuf[5] && 0xD8==ibuf[6]  // mov %eax,%ds
371         &&  0x0F==ibuf[7] && 0x01==ibuf[8] && 020==(070 & ibuf[9]) // lgdtl
372         &&  0xB8==ibuf[14]  // mov $...,%eax
373         &&  0x0F==ibuf[19] && 0xA2==ibuf[20]  // cpuid
374         ) goto head_ok;
375 
376         // cmpw   $0x207,0x206(%esi)  Debian vmlinuz-2.6.24-12-generic
377         if (0==memcmp("\x66\x81\xbe\x06\x02\x00\x00\x07\x02", ibuf, 9)) goto head_ok;
378 
379         // testb  $0x40,0x211(%esi)  Fedora vmlinuz-2.6.25-0.218.rc8.git7.fc9.i686
380         if (0==memcmp("\xf6\x86\x11\x02\x00\x00\x40", ibuf, 7)) goto head_ok;
381 
382         // rex.W prefix for x86_64
383         if (0x48==ibuf[0]) throwCantPack("x86_64 bzImage is not yet supported");
384 
385         throwCantPack("unrecognized kernel architecture; use option '-f' to force packing");
386     head_ok:
387 
388         // FIXME: more checks for special magic bytes in ibuf ???
389         // FIXME: more checks for kernel architecture ???
390 
391         return klen;
392     }
393 
394     return 0;
395 }
396 
397 
readKernel()398 void PackVmlinuzI386::readKernel()
399 {
400     int klen = decompressKernel();
401     if (klen <= 0)
402         throwCantPack("kernel decompression failed");
403     //OutputFile::dump("kernel.img", ibuf, klen);
404 
405     // copy the setup boot code
406     setup_buf.alloc(setup_size);
407     memcpy(setup_buf, obuf, setup_size);
408     //OutputFile::dump("setup.img", setup_buf, setup_size);
409 
410     obuf.dealloc();
411     obuf.allocForCompression(klen);
412 
413     ph.u_len = klen;
414     ph.filter = 0;
415 }
416 
417 
newLinker() const418 Linker* PackVmlinuzI386::newLinker() const
419 {
420     return new ElfLinkerX86;
421 }
422 
423 
424 /*************************************************************************
425 // vmlinuz specific
426 **************************************************************************/
427 
buildLoader(const Filter * ft)428 void PackVmlinuzI386::buildLoader(const Filter *ft)
429 {
430     // prepare loader
431     initLoader(stub_i386_linux_kernel_vmlinuz, sizeof(stub_i386_linux_kernel_vmlinuz));
432     addLoader("LINUZ000",
433               ph.first_offset_found == 1 ? "LINUZ010" : "",
434               ft->id ? "LZCALLT1" : "",
435               "LZIMAGE0",
436               getDecompressorSections(),
437               NULL
438              );
439     if (ft->id)
440     {
441         assert(ft->calls > 0);
442         addLoader("LZCALLT9", NULL);
443         addFilter32(ft->id);
444     }
445     addLoader("LINUZ990,IDENTSTR,UPX1HEAD", NULL);
446 }
447 
448 
pack(OutputFile * fo)449 void PackVmlinuzI386::pack(OutputFile *fo)
450 {
451     readKernel();
452 
453     // prepare filter
454     Filter ft(ph.level);
455     ft.buf_len = ph.u_len;
456     ft.addvalue = physical_start;  // saves 4 bytes in unfilter code
457 
458     // compress
459     upx_compress_config_t cconf; cconf.reset();
460     // limit stack size needed for runtime decompression
461     cconf.conf_lzma.max_num_probs = 1846 + (768 << 4); // ushort: ~28 KiB stack
462     compressWithFilters(&ft, 512, &cconf, getStrategy(ft));
463 
464     const unsigned lsize = getLoaderSize();
465 
466     defineDecompressorSymbols();
467     defineFilterSymbols(&ft);
468     linker->defineSymbol("src_for_decompressor", zimage_offset + lsize);
469     linker->defineSymbol("original_entry", physical_start);
470     linker->defineSymbol("stack_offset", stack_offset_during_uncompression);
471     relocateLoader();
472 
473     MemBuffer loader(lsize);
474     memcpy(loader, getLoader(), lsize);
475     patchPackHeader(loader, lsize);
476 
477     boot_sect_t * const bs = (boot_sect_t *) ((unsigned char *) setup_buf);
478     bs->sys_size = ALIGN_UP(lsize + ph.c_len, 16u) / 16;
479     bs->payload_length = ph.c_len;
480 
481     fo->write(setup_buf, setup_buf.getSize());
482     fo->write(loader, lsize);
483     fo->write(obuf, ph.c_len);
484 #if 0
485     printf("%-13s: setup        : %8ld bytes\n", getName(), (long) setup_buf.getSize());
486     printf("%-13s: loader       : %8ld bytes\n", getName(), (long) lsize);
487     printf("%-13s: compressed   : %8ld bytes\n", getName(), (long) ph.c_len);
488 #endif
489 
490     // verify
491     verifyOverlappingDecompression();
492 
493     // finally check the compression ratio
494     if (!checkFinalCompressionRatio(fo))
495         throwNotCompressible();
496 }
497 
498 
499 /*************************************************************************
500 // bvmlinuz specific
501 **************************************************************************/
502 
buildLoader(const Filter * ft)503 void PackBvmlinuzI386::buildLoader(const Filter *ft)
504 {
505     // prepare loader
506     initLoader(stub_i386_linux_kernel_vmlinuz, sizeof(stub_i386_linux_kernel_vmlinuz));
507     if (0!=page_offset) { // relocatable kernel
508         assert(0==ft->id || 0x40==(0xf0 & ft->id));  // others assume fixed buffer address
509         addLoader("LINUZ000,LINUZ001,LINUZVGA,LINUZ101,LINUZ110",
510             ((0!=config_physical_align) ? "LINUZ120" : "LINUZ130"),
511             "LINUZ140,LZCUTPOI,LINUZ141",
512             (ft->id ? "LINUZ145" : ""),
513             (ph.first_offset_found == 1 ? "LINUZ010" : ""),
514             NULL);
515     }
516     else {
517         addLoader("LINUZ000,LINUZ001,LINUZVGA,LINUZ005",
518               ph.first_offset_found == 1 ? "LINUZ010" : "",
519               (0x40==(0xf0 & ft->id)) ? "LZCKLLT1" : (ft->id ? "LZCALLT1" : ""),
520               "LBZIMAGE,IDENTSTR",
521               "+40", // align the stuff to 4 byte boundary
522               "UPX1HEAD", // 32 byte
523               "LZCUTPOI",
524               NULL);
525         // fake alignment for the start of the decompressor
526         //linker->defineSymbol("LZCUTPOI", 0x1000);
527     }
528 
529     addLoader(getDecompressorSections(), NULL);
530 
531     if (ft->id)
532     {
533             assert(ft->calls > 0);
534         if (0x40==(0xf0 & ft->id)) {
535             addLoader("LZCKLLT9", NULL);
536         }
537         else {
538             addLoader("LZCALLT9", NULL);
539         }
540         addFilter32(ft->id);
541     }
542     if (0!=page_offset) {
543         addLoader("LINUZ150,IDENTSTR,+40,UPX1HEAD", NULL);
544         unsigned const l_len = getLoaderSize();
545         unsigned const c_len = ALIGN_UP(ph.c_len, 4u);
546         unsigned const e_len = getLoaderSectionStart("LINUZ141") -
547                                getLoaderSectionStart("LINUZ110");
548         linker->defineSymbol("compressed_length", c_len);
549         linker->defineSymbol("load_physical_address", physical_start);  // FIXME
550         if (0!=config_physical_align) {
551             linker->defineSymbol("neg_config_physical_align", 0u - config_physical_align);
552         }
553         linker->defineSymbol("neg_length_mov", 0u - ALIGN_UP(c_len + l_len, 4u));
554         linker->defineSymbol("neg_page_offset", 0u - page_offset);
555         //linker->defineSymbol("physical_start", physical_start);
556         linker->defineSymbol("unc_length", ph.u_len);
557         linker->defineSymbol("dec_offset", ph.overlap_overhead + e_len);
558         linker->defineSymbol("unc_offset", ph.overlap_overhead + ph.u_len - c_len);
559     }
560     else {
561         addLoader("LINUZ990", NULL);
562     }
563 }
564 
565 
pack(OutputFile * fo)566 void PackBvmlinuzI386::pack(OutputFile *fo)
567 {
568     readKernel();
569 
570     // prepare filter
571     Filter ft(ph.level);
572     ft.buf_len = (filter_len ? filter_len : (ph.u_len * 3)/5);
573     // May 2008: 3/5 is heuristic to cover most .text but avoid non-instructions.
574     // Otherwise "call trick" filter cannot find a free marker byte,
575     // especially when it searches over tables of data.
576     ft.addvalue = 0;  // The destination buffer might be relocated at runtime.
577 
578     upx_compress_config_t cconf; cconf.reset();
579     // LINUZ001 allows most of low memory as stack for Bvmlinuz
580     cconf.conf_lzma.max_num_probs = (0x90000 - 0x10000)>>1; // ushort: 512 KiB stack
581 
582     compressWithFilters(&ft, 512, &cconf, getStrategy(ft));
583 
584     // align everything to dword boundary - it is easier to handle
585     unsigned c_len = ph.c_len;
586     memset(obuf + c_len, 0, 4);
587     c_len = ALIGN_UP(c_len, 4u);
588 
589     const unsigned lsize = getLoaderSize();
590 
591     if (M_IS_LZMA(ph.method)) {
592         const lzma_compress_result_t *res = &ph.compress_result.result_lzma;
593         upx_uint32_t properties = // lc, lp, pb, dummy
594             (res->lit_context_bits << 0) |
595             (res->lit_pos_bits << 8) |
596             (res->pos_bits << 16);
597         if (linker->bele->isBE()) // big endian - bswap32
598             acc_swab32s(&properties);
599         linker->defineSymbol("lzma_properties", properties);
600         // -2 for properties
601         linker->defineSymbol("lzma_c_len", ph.c_len - 2);
602         linker->defineSymbol("lzma_u_len", ph.u_len);
603         unsigned const stack = getDecompressorWrkmemSize();
604         linker->defineSymbol("lzma_stack_adjust", 0u - stack);
605     }
606 
607     const int e_len = getLoaderSectionStart("LZCUTPOI");
608     assert(e_len > 0);
609 
610     if (0==page_offset) {  // not relocatable kernel
611         const unsigned d_len4 = ALIGN_UP(lsize - e_len, 4u);
612         const unsigned decompr_pos = ALIGN_UP(ph.u_len + ph.overlap_overhead, 16u);
613         const unsigned copy_size = c_len + d_len4;
614         const unsigned edi = decompr_pos + d_len4 - 4;          // copy to
615         const unsigned esi = ALIGN_UP(c_len + lsize, 4u) - 4;   // copy from
616 
617         linker->defineSymbol("decompressor", decompr_pos - bzimage_offset + physical_start);
618         linker->defineSymbol("src_for_decompressor", physical_start + decompr_pos - c_len);
619         linker->defineSymbol("words_to_copy", copy_size / 4);
620         linker->defineSymbol("copy_dest", physical_start + edi);
621         linker->defineSymbol("copy_source", bzimage_offset + esi);
622     }
623 
624     defineFilterSymbols(&ft);
625     defineDecompressorSymbols();
626     if (0==page_offset) {
627         linker->defineSymbol("original_entry", physical_start);
628     }
629     linker->defineSymbol("stack_offset", stack_offset_during_uncompression);
630     relocateLoader();
631 
632     MemBuffer loader(lsize);
633     memcpy(loader, getLoader(), lsize);
634     patchPackHeader(loader, lsize);
635 
636     boot_sect_t * const bs = (boot_sect_t *) ((unsigned char *) setup_buf);
637     bs->sys_size = (ALIGN_UP(lsize + c_len, 16u) / 16);
638 
639     fo->write(setup_buf, setup_buf.getSize());
640 
641     unsigned const e_pfx = (0==page_offset) ? 0 : getLoaderSectionStart("LINUZ110");
642     if (0!=page_offset) {
643         fo->write(loader, e_pfx);
644     }
645     else {
646         fo->write(loader, e_len);
647     }
648     fo->write(obuf, c_len);
649     if (0!=page_offset) {
650         fo->write(loader + e_pfx, e_len - e_pfx);
651     }
652     fo->write(loader + e_len, lsize - e_len);
653 #if 0
654     printf("%-13s: setup        : %8ld bytes\n", getName(), (long) setup_buf.getSize());
655     printf("%-13s: entry        : %8ld bytes\n", getName(), (long) e_len);
656     printf("%-13s: compressed   : %8ld bytes\n", getName(), (long) c_len);
657     printf("%-13s: decompressor : %8ld bytes\n", getName(), (long) (lsize - e_len));
658 #endif
659 
660     // verify
661     verifyOverlappingDecompression();
662 
663     // finally check the compression ratio
664     if (!checkFinalCompressionRatio(fo))
665         throwNotCompressible();
666 }
667 
668 
669 /*************************************************************************
670 // unpack
671 **************************************************************************/
672 
canUnpack()673 int PackVmlinuzI386::canUnpack()
674 {
675     if (readFileHeader() != getFormat())
676         return false;
677     fi->seek(setup_size, SEEK_SET);
678     return readPackHeader(1024) ? 1 : -1;
679 }
680 
681 
unpack(OutputFile * fo)682 void PackVmlinuzI386::unpack(OutputFile *fo)
683 {
684     // no uncompression support for this format, so that
685     // it is possible to remove the original deflate code (>10 KiB)
686 
687     // FIXME: but we could write the uncompressed "vmlinux" image
688 
689     ibuf.alloc(ph.c_len);
690     obuf.allocForUncompression(ph.u_len);
691 
692     fi->seek(setup_size + ph.buf_offset + ph.getPackHeaderSize(), SEEK_SET);
693     fi->readx(ibuf, ph.c_len);
694 
695     // decompress
696     decompress(ibuf, obuf);
697 
698     // unfilter
699     Filter ft(ph.level);
700     ft.init(ph.filter, physical_start);
701     ft.cto = (unsigned char) ph.filter_cto;
702     ft.unfilter(obuf, ph.u_len);
703 
704     // write decompressed file
705     if (fo)
706     {
707         throwCantUnpack("build a new kernel instead :-)");
708         //fo->write(obuf, ph.u_len);
709     }
710 }
711 
712 
PackVmlinuzARMEL(InputFile * f)713 PackVmlinuzARMEL::PackVmlinuzARMEL(InputFile *f) :
714     super(f), setup_size(0), filter_len(0)
715 {
716     bele = &N_BELE_RTP::le_policy;
717 }
718 
getCompressionMethods(int method,int level) const719 const int *PackVmlinuzARMEL::getCompressionMethods(int method, int level) const
720 {
721     return Packer::getDefaultCompressionMethods_8(method, level);
722 }
723 
getFilters() const724 const int *PackVmlinuzARMEL::getFilters() const
725 {
726     static const int f50[] = { 0x50, FT_END };
727     return f50;
728 }
729 
getStrategy(Filter &)730 int PackVmlinuzARMEL::getStrategy(Filter &/*ft*/)
731 {
732     // If user specified the filter, then use it (-2==filter_strategy).
733     // Else try the first two filters, and pick the better (2==filter_strategy).
734     return (opt->no_filter ? -3 : ((opt->filter > 0) ? -2 : 2));
735 }
736 
canPack()737 bool PackVmlinuzARMEL::canPack()
738 {
739     return readFileHeader() == getFormat();
740 }
741 
readFileHeader()742 int PackVmlinuzARMEL::readFileHeader()
743 {
744     unsigned int hdr[8];
745 
746     fi->readx(hdr, sizeof(hdr));
747     for (int j=0; j < 8; ++j) {
748         if (0xe1a00000!=get_te32(&hdr[j])) {
749             return 0;
750         }
751     }
752     return UPX_F_VMLINUZ_ARMEL;
753 }
754 
decompressKernel()755 int PackVmlinuzARMEL::decompressKernel()
756 {
757     // read whole kernel image
758     obuf.alloc(file_size);
759     fi->seek(0, SEEK_SET);
760     fi->readx(obuf, file_size);
761 
762     //checkAlreadyPacked(obuf + setup_size, UPX_MIN(file_size - setup_size, (off_t)1024));
763 
764     // Find head.S:
765     //      bl decompress_kernel  # 0xeb......
766     //      b call_kernel         # 0xea......
767     //LC0: .word LC0              # self!
768     unsigned decompress_kernel = 0;
769     unsigned caller1 = 0;
770     unsigned caller2 = 0;
771     unsigned got_start = 0;
772     unsigned got_end = 0;
773     for (unsigned j = 0; j < 0x400; j+=4) {
774         unsigned w;
775         if (j!=get_te32(j + obuf)) {
776             continue;
777         }
778         if (0xea000000!=(0xff000000&    get_te32(j - 4 + obuf))
779         ||  0xeb000000!=(0xff000000&(w= get_te32(j - 8 + obuf))) ) {
780             continue;
781         }
782         caller1 = j - 8;
783         decompress_kernel = ((0x00ffffff & w)<<2) + 8+ caller1;
784         for (unsigned k = 12; k<=128; k+=4) {
785             w = get_te32(j - k + obuf);
786             if (0xeb000000==(0xff000000 & w)
787             &&  decompress_kernel==(((0x00ffffff & w)<<2) + 8+ j - k) ) {
788                 caller2 = j - k;
789                 break;
790             }
791         }
792         got_start = get_te32(5*4 + j + obuf);
793         got_end   = get_te32(6*4 + j + obuf);
794 #if 0  /*{*/
795         printf("decompress_kernel=0x%x  got_start=0x%x  got_end=0x%x\n",
796             decompress_kernel, got_start, got_end);
797 #endif  /*}*/
798         break;
799     }
800     if (0==decompress_kernel) {
801         return 0;
802     }
803 
804     // Find first subroutine that is called by decompress_kernel,
805     // which we will consider to be the start of the gunzip module
806     // and the end of the non-gunzip modules.
807     for (unsigned j = decompress_kernel; j < (unsigned)file_size; j+=4) {
808         unsigned w = get_te32(j + obuf);
809         if (0xeb800000==(0xff800000 & w)) {
810             setup_size = 8+ ((0xff000000 | w)<<2) + j;
811             // Move the GlobalOffsetTable.
812             for (unsigned k = got_start; k < got_end; k+=4) {
813                 w = get_te32(k + obuf);
814                 // FIXME: must relocate w
815                 set_te32(k - got_start + setup_size + obuf, w);
816             }
817             setup_size += got_end - got_start;
818             set_te32(&obuf[caller1], 0xeb000000 |
819                 (0x00ffffff & ((setup_size - (8+ caller1))>>2)) );
820             set_te32(&obuf[caller2], 0xeb000000 |
821                 (0x00ffffff & ((setup_size - (8+ caller2))>>2)) );
822             break;
823         }
824     }
825 
826     for (int gzoff = 0; gzoff < 0x4000; gzoff+=4) {
827         // find gzip header (2 bytes magic + 1 byte method "deflated")
828         int off = find(obuf + gzoff, file_size - gzoff, "\x1F\x8B\x08", 3);
829         if (off < 0 || 0!=(3u & off))
830             break;  // not found, or not word-aligned
831         gzoff += off;
832         const int gzlen = file_size - gzoff;
833         if (gzlen < 256)
834             break;
835         // check gzip flag byte
836         unsigned char flags = obuf[gzoff + 3];
837         if ((flags & 0xe0) != 0)        // reserved bits set
838             continue;
839         //printf("found gzip header at offset %d\n", gzoff);
840 
841         // try to decompress
842         int klen;
843         int fd;
844         off_t fd_pos;
845         for (;;)
846         {
847             klen = -1;
848             fd_pos = -1;
849             // open
850             fi->seek(gzoff, SEEK_SET);
851             fd = dup(fi->getFd());
852             if (fd < 0)
853                 break;
854             gzFile zf = gzdopen(fd, "rb");
855             if (zf == NULL)
856                 break;
857             // estimate gzip-decompressed kernel size & alloc buffer
858             if (ibuf.getSize() == 0)
859                 ibuf.alloc(gzlen * 3);
860             // decompress
861             klen = gzread(zf, ibuf, ibuf.getSize());
862             fd_pos = lseek(fd, 0, SEEK_CUR);
863             gzclose(zf);
864             fd = -1;
865             if (klen != (int)ibuf.getSize())
866                 break;
867             // realloc and try again
868             unsigned const s = ibuf.getSize();
869             ibuf.dealloc();
870             ibuf.alloc(3 * s / 2);
871         }
872         if (fd >= 0)
873             (void) close(fd);
874         if (klen <= 0)
875             continue;
876 
877         if (klen <= gzlen)
878             continue;
879 
880         if (opt->force > 0)
881             return klen;
882 
883         // some checks
884         if (fd_pos != file_size) {
885             //printf("fd_pos: %ld, file_size: %ld\n", (long)fd_pos, (long)file_size);
886         }
887 
888     //head_ok:
889 
890         // FIXME: more checks for special magic bytes in ibuf ???
891         // FIXME: more checks for kernel architecture ???
892 
893         return klen;
894     }
895 
896     return 0;
897 }
898 
readKernel()899 void PackVmlinuzARMEL::readKernel()
900 {
901     int klen = decompressKernel();
902     if (klen <= 0)
903         throwCantPack("kernel decompression failed");
904     //OutputFile::dump("kernel.img", ibuf, klen);
905 
906     // copy the setup boot code
907     setup_buf.alloc(setup_size);
908     memcpy(setup_buf, obuf, setup_size);
909     //OutputFile::dump("setup.img", setup_buf, setup_size);
910 
911     obuf.dealloc();
912     obuf.allocForCompression(klen);
913 
914     ph.u_len = klen;
915     ph.filter = 0;
916 }
917 
newLinker() const918 Linker* PackVmlinuzARMEL::newLinker() const
919 {
920     return new ElfLinkerArmLE;
921 }
922 
923 static const
924 #include "stub/arm.v5a-linux.kernel.vmlinux.h"
925 static const
926 #include "stub/arm.v5a-linux.kernel.vmlinuz-head.h"
927 
buildLoader(const Filter * ft)928 void PackVmlinuzARMEL::buildLoader(const Filter *ft)
929 {
930     // prepare loader; same as vmlinux (with 'x')
931     initLoader(stub_arm_v5a_linux_kernel_vmlinux, sizeof(stub_arm_v5a_linux_kernel_vmlinux));
932     addLoader("LINUX000", NULL);
933     if (ft->id) {
934         assert(ft->calls > 0);
935         addLoader("LINUX010", NULL);
936     }
937     addLoader("LINUX020", NULL);
938     if (ft->id) {
939         addFilter32(ft->id);
940     }
941     addLoader("LINUX030", NULL);
942          if (ph.method == M_NRV2E_8) addLoader("NRV2E", NULL);
943     else if (ph.method == M_NRV2B_8) addLoader("NRV2B", NULL);
944     else if (ph.method == M_NRV2D_8) addLoader("NRV2D", NULL);
945     else if (M_IS_LZMA(ph.method))   addLoader("LZMA_ELF00",
946         (opt->small ? "LZMA_DEC10" : "LZMA_DEC20"), "LZMA_DEC30", NULL);
947     else throwBadLoader();
948     addLoader("IDENTSTR,UPX1HEAD", NULL);
949 
950     // To debug (2008-09-14):
951     //   Build gdb-6.8-21.fc9.src.rpm; ./configure --target=arm-none-elf; make
952     //     Contains the fix for http://bugzilla.redhat.com/show_bug.cgi?id=436037
953     //   Install qemu-0.9.1-6.fc9.i386.rpm
954     //   qemu-system-arm -s -S -kernel <file> -nographic
955     //   (gdb) target remote localhost:1234
956     //   A very small boot loader runs at pc=0x0; the kernel is at 0x10000 (64KiB).
957 }
958 
defineDecompressorSymbols()959 void PackVmlinuzARMEL::defineDecompressorSymbols()
960 {
961     super::defineDecompressorSymbols();
962     linker->defineSymbol(  "COMPRESSED_LENGTH", ph.c_len);
963     linker->defineSymbol("UNCOMPRESSED_LENGTH", ph.u_len);
964     linker->defineSymbol("METHOD", ph.method);
965 }
966 
write_vmlinuz_head(OutputFile * fo)967 unsigned PackVmlinuzARMEL::write_vmlinuz_head(OutputFile *fo)
968 { // First word from vmlinuz-head.S
969     fo->write(&stub_arm_v5a_linux_kernel_vmlinuz_head[0], 4);
970 
971     // Second word
972     upx_uint32_t tmp_u32;
973     unsigned const t = (0xff000000 &
974             get_te32(&stub_arm_v5a_linux_kernel_vmlinuz_head[4]))
975         | (0x00ffffff & (0u - 1 + ((3+ ph.c_len)>>2)));
976     set_te32(&tmp_u32, t);
977     fo->write(&tmp_u32, 4);
978 
979     return sizeof(stub_arm_v5a_linux_kernel_vmlinuz_head);
980 }
981 
pack(OutputFile * fo)982 void PackVmlinuzARMEL::pack(OutputFile *fo)
983 {
984     readKernel();
985 
986     // prepare filter
987     Filter ft(ph.level);
988     ft.buf_len = ph.u_len;
989     ft.addvalue = 0;
990 
991     // compress
992     upx_compress_config_t cconf; cconf.reset();
993     // limit stack size needed for runtime decompression
994     cconf.conf_lzma.max_num_probs = 1846 + (768 << 5); // ushort: 52,844 byte stack
995     compressWithFilters(&ft, 512, &cconf, getStrategy(ft));
996 
997     const unsigned lsize = getLoaderSize();
998 
999     defineDecompressorSymbols();
1000     defineFilterSymbols(&ft);
1001     relocateLoader();
1002 
1003     MemBuffer loader(lsize);
1004     memcpy(loader, getLoader(), lsize);
1005     patchPackHeader(loader, lsize);
1006 
1007 //    boot_sect_t * const bs = (boot_sect_t *) ((unsigned char *) setup_buf);
1008 //    bs->sys_size = ALIGN_UP(lsize + ph.c_len, 16u) / 16;
1009 //    bs->payload_length = ph.c_len;
1010 
1011     fo->write(setup_buf, setup_buf.getSize());
1012     write_vmlinuz_head(fo);
1013     fo->write(obuf, ph.c_len);
1014     unsigned const zero = 0;
1015     fo->write((void const *)&zero, 3u & (0u - ph.c_len));
1016     fo->write(loader, lsize);
1017 #if 0
1018     printf("%-13s: setup        : %8ld bytes\n", getName(), (long) setup_buf.getSize());
1019     printf("%-13s: loader       : %8ld bytes\n", getName(), (long) lsize);
1020     printf("%-13s: compressed   : %8ld bytes\n", getName(), (long) ph.c_len);
1021 #endif
1022 
1023     // verify
1024     verifyOverlappingDecompression();
1025 
1026     // finally check the compression ratio
1027     if (!checkFinalCompressionRatio(fo))
1028         throwNotCompressible();
1029 }
1030 
canUnpack()1031 int PackVmlinuzARMEL::canUnpack()
1032 {
1033     if (readFileHeader() != getFormat())
1034         return false;
1035     fi->seek(setup_size, SEEK_SET);
1036     return readPackHeader(1024) ? 1 : -1;
1037 }
1038 
unpack(OutputFile * fo)1039 void PackVmlinuzARMEL::unpack(OutputFile *fo)
1040 {
1041     // no uncompression support for this format, so that
1042     // it is possible to remove the original deflate code (>10 KiB)
1043 
1044     // FIXME: but we could write the uncompressed "vmlinux" image
1045 
1046     ibuf.alloc(ph.c_len);
1047     obuf.allocForUncompression(ph.u_len);
1048 
1049     fi->seek(setup_size + ph.buf_offset + ph.getPackHeaderSize(), SEEK_SET);
1050     fi->readx(ibuf, ph.c_len);
1051 
1052     // decompress
1053     decompress(ibuf, obuf);
1054 
1055     // unfilter
1056     Filter ft(ph.level);
1057     ft.init(ph.filter, 0);
1058     ft.cto = (unsigned char) ph.filter_cto;
1059     ft.unfilter(obuf, ph.u_len);
1060 
1061     // write decompressed file
1062     if (fo)
1063     {
1064         throwCantUnpack("build a new kernel instead :-)");
1065         //fo->write(obuf, ph.u_len);
1066     }
1067 }
1068 
1069 /* vim:set ts=4 sw=4 et: */
1070