/src/upx/src/p_djgpp2.cpp
Line | Count | Source |
1 | | /* p_djgpp2.cpp -- |
2 | | |
3 | | This file is part of the UPX executable compressor. |
4 | | |
5 | | Copyright (C) Markus Franz Xaver Johannes Oberhumer |
6 | | Copyright (C) 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 | | #include "conf.h" |
29 | | #include "file.h" |
30 | | #include "filter.h" |
31 | | #include "packer.h" |
32 | | #include "p_djgpp2.h" |
33 | | #define WANT_EHDR_ENUM 1 |
34 | | #include "p_elf_enum.h" |
35 | | #include "linker.h" |
36 | | |
37 | | static const CLANG_FORMAT_DUMMY_STATEMENT |
38 | | #include "stub/i386-dos32.djgpp2.h" |
39 | | static const CLANG_FORMAT_DUMMY_STATEMENT |
40 | | #include "stub/i386-dos32.djgpp2-stubify.h" |
41 | | |
42 | | /************************************************************************* |
43 | | // |
44 | | **************************************************************************/ |
45 | | |
46 | 42.0k | PackDjgpp2::PackDjgpp2(InputFile *f) : super(f), coff_offset(0) { |
47 | 42.0k | bele = &N_BELE_RTP::le_policy; |
48 | 42.0k | COMPILE_TIME_ASSERT(sizeof(dos_header_t) == 64) |
49 | 42.0k | COMPILE_TIME_ASSERT(sizeof(external_scnhdr_t) == 40) |
50 | 42.0k | COMPILE_TIME_ASSERT(sizeof(coff_header_t) == 0xa8) |
51 | 42.0k | COMPILE_TIME_ASSERT_ALIGNED1(dos_header_t) |
52 | 42.0k | COMPILE_TIME_ASSERT_ALIGNED1(external_scnhdr_t) |
53 | 42.0k | COMPILE_TIME_ASSERT_ALIGNED1(coff_header_t) |
54 | 42.0k | COMPILE_TIME_ASSERT(sizeof(stub_i386_dos32_djgpp2_stubify) == 2048) |
55 | 42.0k | COMPILE_TIME_ASSERT(STUB_I386_DOS32_DJGPP2_STUBIFY_ADLER32 == 0xbf689ba8) |
56 | 42.0k | COMPILE_TIME_ASSERT(STUB_I386_DOS32_DJGPP2_STUBIFY_CRC32 == 0x2ae982b2) |
57 | | // printf("0x%08x\n", upx_adler32(stubify_stub, sizeof(stubify_stub))); |
58 | | // assert(upx_adler32(stubify_stub, sizeof(stubify_stub)) == STUBIFY_STUB_ADLER32); |
59 | 42.0k | } |
60 | | |
61 | 0 | Linker *PackDjgpp2::newLinker() const { return new ElfLinkerX86; } |
62 | | |
63 | 0 | const int *PackDjgpp2::getCompressionMethods(int method, int level) const { |
64 | 0 | return Packer::getDefaultCompressionMethods_le32(method, level); |
65 | 0 | } |
66 | | |
67 | 0 | const int *PackDjgpp2::getFilters() const { |
68 | 0 | static const int filters[] = {0x26, 0x24, 0x49, 0x46, 0x16, 0x13, 0x14, |
69 | 0 | 0x11, FT_ULTRA_BRUTE, 0x25, 0x15, 0x12, FT_END}; |
70 | 0 | return filters; |
71 | 0 | } |
72 | | |
73 | | unsigned PackDjgpp2::findOverlapOverhead(const byte *buf, const byte *tbuf, unsigned range, |
74 | 0 | unsigned upper_limit) const { |
75 | 0 | unsigned o = super::findOverlapOverhead(buf, tbuf, range, upper_limit); |
76 | 0 | o = (o + 0x3ff) & ~0x1ff; |
77 | 0 | return o; |
78 | 0 | } |
79 | | |
80 | 0 | void PackDjgpp2::buildLoader(const Filter *ft) { |
81 | | // prepare loader |
82 | 0 | initLoader(EM_386, stub_i386_dos32_djgpp2, sizeof(stub_i386_dos32_djgpp2)); |
83 | 0 | addLoader("IDENTSTR,DJ2MAIN1", ft->id ? "DJCALLT1" : "", |
84 | 0 | ph.first_offset_found == 1 ? "DJ2MAIN2" : "", |
85 | 0 | M_IS_LZMA(ph.method) ? "LZMA_INIT_STACK" : "", getDecompressorSections(), |
86 | 0 | M_IS_LZMA(ph.method) ? "LZMA_DONE_STACK" : "", "DJ2BSS00"); |
87 | 0 | if (ft->id) { |
88 | 0 | assert(ft->calls > 0); |
89 | 0 | addLoader("DJCALLT2"); |
90 | 0 | addFilter32(ft->id); |
91 | 0 | } |
92 | 0 | addLoader("DJRETURN,+40C,UPX1HEAD"); |
93 | 0 | } |
94 | | |
95 | | /************************************************************************* |
96 | | // util |
97 | | **************************************************************************/ |
98 | | |
99 | 18 | void PackDjgpp2::handleStub(OutputFile *fo) { |
100 | 18 | if (fo && !opt->djgpp2_coff.coff) { |
101 | 13 | if (coff_offset > 0) { |
102 | | // copy stub from exe |
103 | 9 | Packer::handleStub(fi, fo, coff_offset); |
104 | 9 | } else { |
105 | | // "stubify" stub |
106 | 4 | info("Adding stub: %zd bytes", sizeof(stub_i386_dos32_djgpp2_stubify)); |
107 | 4 | fo->write(stub_i386_dos32_djgpp2_stubify, sizeof(stub_i386_dos32_djgpp2_stubify)); |
108 | 4 | } |
109 | 13 | } |
110 | 18 | } |
111 | | |
112 | 61 | static bool is_dlm(InputFile *fi, unsigned coff_offset) { |
113 | 61 | byte buf[4]; |
114 | 61 | unsigned off; |
115 | | |
116 | 61 | try { |
117 | 61 | fi->seek(coff_offset, SEEK_SET); |
118 | 61 | fi->readx(buf, 4); |
119 | 61 | off = get_le32(buf); |
120 | 61 | if (off > coff_offset + 4) |
121 | 33 | return false; |
122 | 28 | fi->seek(off, SEEK_SET); |
123 | 28 | fi->readx(buf, 4); |
124 | 28 | if (memcmp(buf, "DLMF", 4) == 0) |
125 | 3 | return true; |
126 | 28 | } catch (const IOException &) { |
127 | 0 | } |
128 | 25 | return false; |
129 | 61 | } |
130 | | |
131 | 2 | static void handle_allegropak(InputFile *fi, OutputFile *fo) { |
132 | 2 | byte b[8]; |
133 | 2 | int pfsize = 0; |
134 | | |
135 | 2 | try { |
136 | 2 | fi->seek(-8, SEEK_END); |
137 | 2 | fi->readx(b, 8); |
138 | 2 | if (memcmp(b, "slh+", 4) != 0) |
139 | 2 | return; |
140 | 0 | pfsize = get_be32_signed(b + 4); |
141 | 0 | if (pfsize <= 8 || pfsize >= fi->st.st_size) |
142 | 0 | return; |
143 | 0 | fi->seek(-pfsize, SEEK_END); |
144 | 0 | } catch (const IOException &) { |
145 | 0 | return; |
146 | 0 | } |
147 | 0 | MemBuffer buf(0x4000); |
148 | 0 | while (pfsize > 0) { |
149 | 0 | const int len = UPX_MIN(pfsize, (int) buf.getSize()); |
150 | 0 | fi->readx(buf, len); |
151 | 0 | fo->write(buf, len); |
152 | 0 | pfsize -= len; |
153 | 0 | } |
154 | 0 | } |
155 | | |
156 | 42.0k | int PackDjgpp2::readFileHeader() { |
157 | 42.0k | dos_header_t dos_hdr; |
158 | | |
159 | 42.0k | fi->seek(0, SEEK_SET); |
160 | 42.0k | fi->readx(&dos_hdr, sizeof(dos_hdr)); |
161 | 42.0k | if (get_le16(&dos_hdr.e_magic) == 0x5a4d) { // MZ exe signature, stubbed? |
162 | 1.41k | byte magic[8]; |
163 | 1.41k | fi->seek(16 * get_le16(&dos_hdr.e_cparhdr), SEEK_SET); |
164 | 1.41k | fi->readx(magic, 8); |
165 | 1.41k | if (memcmp("go32stub", magic, 8) != 0) |
166 | 1.01k | return 0; // not V2 image |
167 | 1.41k | } |
168 | 41.0k | coff_offset = 512 * get_le16(&dos_hdr.e_cp); |
169 | 41.0k | if (get_le16(&dos_hdr.e_cblp) != 0) |
170 | 39.5k | coff_offset += get_le16(&dos_hdr.e_cblp) - 512; |
171 | 41.0k | fi->seek(coff_offset, SEEK_SET); |
172 | 41.0k | if (fi->read(&coff_hdr, sizeof(coff_hdr)) != sizeof(coff_hdr)) |
173 | 15 | throwCantPack("skipping djgpp symlink"); |
174 | 40.9k | if (coff_hdr.f_magic != 0x014c) // I386MAGIC |
175 | 1.52k | return 0; |
176 | 39.4k | if ((coff_hdr.f_flags & 2) == 0) // F_EXEC - COFF executable |
177 | 3 | return 0; |
178 | 39.4k | if (coff_hdr.a_magic != 0413) // ZMAGIC - demand load format |
179 | 9 | return 0; |
180 | | // FIXME: check for Linux etc. |
181 | | |
182 | 39.4k | text = &coff_hdr.sh[0]; |
183 | 39.4k | data = &coff_hdr.sh[1]; |
184 | 39.4k | bss = &coff_hdr.sh[2]; |
185 | 39.4k | return UPX_F_DJGPP2_COFF; |
186 | 39.4k | } |
187 | | |
188 | | // "strip" debug info |
189 | 0 | void PackDjgpp2::stripDebug() { |
190 | 0 | coff_hdr.f_symptr = 0; |
191 | 0 | coff_hdr.f_nsyms = 0; |
192 | 0 | coff_hdr.f_flags = 0x10f; // 0x100: "32 bit machine: LSB first" |
193 | 0 | memset(text->misc, 0, 12); |
194 | 0 | } |
195 | | |
196 | | /************************************************************************* |
197 | | // |
198 | | **************************************************************************/ |
199 | | |
200 | 0 | tribool PackDjgpp2::canPack() { |
201 | 0 | if (!readFileHeader()) |
202 | 0 | return false; |
203 | 0 | if (is_dlm(fi, coff_offset)) |
204 | 0 | throwCantPack("can't handle DLM"); |
205 | | |
206 | 0 | if (opt->force == 0) |
207 | 0 | if (text->size != coff_hdr.a_tsize || data->size != coff_hdr.a_dsize) |
208 | 0 | throwAlreadyPacked(); |
209 | | |
210 | | // Check for gap in vaddr between text and data, or between data and bss. |
211 | 0 | if (text->vaddr + text->size != data->vaddr || data->vaddr + data->size != bss->vaddr) { |
212 | | // "Non-standard" layout of text,data,bss: not contiguous in vaddr. |
213 | | // But should be OK if no overlap. |
214 | | |
215 | | // Check for no overlap of text and data: |
216 | | // neither by vaddr, nor by image data |
217 | 0 | if (text->vaddr + text->size <= data->vaddr && |
218 | 0 | data->scnptr - text->scnptr <= data->vaddr - text->vaddr) { |
219 | | // Examples: Quake1; FreePascal(DOS) install.exe (github-issue45) |
220 | | // Hack: enlarge text image data to eliminate the gap. |
221 | 0 | text->size = coff_hdr.a_tsize = data->scnptr - text->scnptr; |
222 | | // But complain if this causes overlap in vaddr |
223 | 0 | if (text->vaddr + text->size > data->vaddr) |
224 | 0 | throwAlreadyPacked(); |
225 | 0 | } else |
226 | 0 | throwAlreadyPacked(); |
227 | 0 | } |
228 | | // FIXME: check for Linux etc. |
229 | 0 | return true; |
230 | 0 | } |
231 | | |
232 | | /************************************************************************* |
233 | | // |
234 | | **************************************************************************/ |
235 | | |
236 | 0 | void PackDjgpp2::pack(OutputFile *fo) { |
237 | 0 | handleStub(fo); |
238 | | |
239 | | // patch coff header #1: "strip" debug info |
240 | 0 | stripDebug(); |
241 | | |
242 | | // read file |
243 | 0 | const unsigned size = text->size + data->size; |
244 | 0 | const unsigned tpos = text->scnptr; |
245 | 0 | const unsigned hdrsize = 20 + 28 + mem_size(sizeof(external_scnhdr_t), coff_hdr.f_nscns); |
246 | 0 | const unsigned usize = size + hdrsize; |
247 | 0 | if (hdrsize < sizeof(coff_hdr) || hdrsize > tpos) |
248 | 0 | throwCantPack("coff header error"); |
249 | | |
250 | 0 | ibuf.alloc(usize); |
251 | 0 | obuf.allocForCompression(usize); |
252 | |
|
253 | 0 | fi->seek(coff_offset, SEEK_SET); |
254 | 0 | fi->readx(ibuf, hdrsize); // orig. coff header |
255 | 0 | fi->seek(coff_offset + tpos, SEEK_SET); |
256 | 0 | fi->readx(ibuf + hdrsize, size); |
257 | | |
258 | | // prepare packheader |
259 | 0 | ph.u_len = usize; |
260 | | // prepare filter |
261 | 0 | Filter ft(ph.level); |
262 | 0 | ft.buf_len = usize - data->size; |
263 | 0 | ft.addvalue = text->vaddr - hdrsize; |
264 | | // compress |
265 | 0 | upx_compress_config_t cconf; |
266 | 0 | cconf.reset(); |
267 | | // limit stack size needed for runtime decompression |
268 | 0 | cconf.conf_lzma.max_num_probs = 1846 + (768 << 4); // ushort: ~28 KiB stack |
269 | 0 | compressWithFilters(&ft, 512, &cconf); |
270 | | |
271 | | // patch coff header #2 |
272 | 0 | const unsigned lsize = getLoaderSize(); |
273 | 0 | assert(lsize % 4 == 0); |
274 | 0 | text->size = lsize; // new size of .text |
275 | 0 | data->size = ph.c_len; // new size of .data |
276 | |
|
277 | 0 | unsigned stack = 1024 + ph.overlap_overhead + getDecompressorWrkmemSize(); |
278 | 0 | stack = ALIGN_UP(stack, 16u); |
279 | 0 | if (bss->size < stack) // give it a .bss |
280 | 0 | bss->size = stack; |
281 | |
|
282 | 0 | text->scnptr = sizeof(coff_hdr); |
283 | 0 | data->scnptr = text->scnptr + text->size; |
284 | 0 | data->vaddr = bss->vaddr + ((data->scnptr + data->size) & 0x1ff) - data->size + |
285 | 0 | ph.overlap_overhead - 0x200; |
286 | 0 | coff_hdr.f_nscns = 3; |
287 | |
|
288 | 0 | linker->defineSymbol("original_entry", coff_hdr.a_entry); |
289 | 0 | linker->defineSymbol("length_of_bss", ph.overlap_overhead / 4); |
290 | 0 | defineDecompressorSymbols(); |
291 | | // Just need no overlap; non-contiguous (gap length > 0)) is OK |
292 | 0 | assert(bss->vaddr >= ((size + 0x1ff) & ~0x1ff) + (text->vaddr & ~0x1ff)); |
293 | 0 | linker->defineSymbol("stack_for_lzma", bss->vaddr + bss->size); |
294 | 0 | linker->defineSymbol("start_of_uncompressed", text->vaddr - hdrsize); |
295 | 0 | linker->defineSymbol("start_of_compressed", data->vaddr); |
296 | 0 | defineFilterSymbols(&ft); |
297 | | |
298 | | // we should not overwrite our decompressor during unpacking |
299 | | // the original coff header (which is put just before the |
300 | | // beginning of the original .text section) |
301 | 0 | assert(text->vaddr > hdrsize + lsize + sizeof(coff_hdr)); |
302 | | |
303 | | // patch coff header #3 |
304 | 0 | text->vaddr = sizeof(coff_hdr); |
305 | 0 | coff_hdr.a_entry = sizeof(coff_hdr) + getLoaderSection("DJ2MAIN1"); |
306 | 0 | bss->vaddr += ph.overlap_overhead; |
307 | 0 | bss->size -= ph.overlap_overhead; |
308 | | |
309 | | // because of a feature (bug?) in stub.asm we need some padding |
310 | 0 | memcpy(obuf + data->size, "UPX", 3); |
311 | 0 | data->size = ALIGN_UP(data->size, 4u); |
312 | |
|
313 | 0 | linker->defineSymbol("DJ2MAIN1", coff_hdr.a_entry); |
314 | 0 | relocateLoader(); |
315 | | |
316 | | // prepare loader |
317 | 0 | MemBuffer loader(lsize); |
318 | 0 | memcpy(loader, getLoader(), lsize); |
319 | 0 | patchPackHeader(loader, lsize); |
320 | | |
321 | | // write coff header, loader and compressed file |
322 | 0 | fo->write(&coff_hdr, sizeof(coff_hdr)); |
323 | 0 | fo->write(loader, lsize); |
324 | 0 | if (opt->debug.dump_stub_loader) |
325 | 0 | OutputFile::dump(opt->debug.dump_stub_loader, loader, lsize); |
326 | 0 | fo->write(obuf, data->size); |
327 | | #if 0 |
328 | | printf("%-13s: coff hdr : %8d bytes\n", getName(), (int) sizeof(coff_hdr)); |
329 | | printf("%-13s: loader : %8d bytes\n", getName(), (int) lsize); |
330 | | printf("%-13s: compressed : %8d bytes\n", getName(), (int) data->size); |
331 | | #endif |
332 | | |
333 | | // verify |
334 | 0 | verifyOverlappingDecompression(); |
335 | | |
336 | | // handle overlay |
337 | | // FIXME: only Allegro pakfiles are supported |
338 | 0 | handle_allegropak(fi, fo); |
339 | | |
340 | | // finally check the compression ratio |
341 | 0 | if (!checkFinalCompressionRatio(fo)) |
342 | 0 | throwNotCompressible(); |
343 | 0 | } |
344 | | |
345 | | /************************************************************************* |
346 | | // |
347 | | **************************************************************************/ |
348 | | |
349 | 42.0k | tribool PackDjgpp2::canUnpack() { |
350 | 42.0k | if (!readFileHeader()) |
351 | 2.55k | return false; |
352 | 39.4k | if (is_dlm(fi, coff_offset)) |
353 | 3 | throwCantUnpack("can't handle DLM"); |
354 | 39.4k | fi->seek(coff_offset, SEEK_SET); |
355 | 39.4k | return readPackHeader(4096) ? 1 : -1; |
356 | 39.4k | } |
357 | | |
358 | | /************************************************************************* |
359 | | // |
360 | | **************************************************************************/ |
361 | | |
362 | 18 | void PackDjgpp2::unpack(OutputFile *fo) { |
363 | 18 | handleStub(fo); |
364 | | |
365 | 18 | ibuf.alloc(ph.c_len); |
366 | 18 | obuf.allocForDecompression(ph.u_len); |
367 | | |
368 | 18 | fi->seek(coff_offset + ph.buf_offset + ph.getPackHeaderSize(), SEEK_SET); |
369 | 18 | fi->readx(ibuf, ph.c_len); |
370 | | |
371 | | // decompress |
372 | 18 | decompress(ibuf, obuf); |
373 | | |
374 | 18 | coff_header_t *const chdr = (coff_header_t *) raw_bytes(obuf, sizeof(coff_header_t)); |
375 | 18 | text = &chdr->sh[0]; |
376 | 18 | data = &chdr->sh[1]; |
377 | 18 | bss = &chdr->sh[2]; |
378 | | |
379 | 18 | const unsigned hdrsize = 20 + 28 + mem_size(sizeof(external_scnhdr_t), chdr->f_nscns); |
380 | 18 | if (hdrsize < sizeof(coff_hdr) || hdrsize > text->scnptr || hdrsize > ph.u_len) |
381 | 0 | throwCantUnpack("coff header error"); |
382 | | |
383 | 18 | unsigned addvalue; |
384 | 18 | if (ph.version >= 14) |
385 | 4 | addvalue = text->vaddr - hdrsize; |
386 | 14 | else |
387 | 14 | addvalue = text->vaddr & ~0x1ff; // for old versions |
388 | | |
389 | | // unfilter |
390 | 18 | if (ph.filter) { |
391 | 4 | Filter ft(ph.level); |
392 | 4 | ft.init(ph.filter, addvalue); |
393 | 4 | ft.cto = (byte) ph.filter_cto; |
394 | 4 | if (ph.version < 11) { |
395 | 0 | byte ctobuf[4]; |
396 | 0 | fi->readx(ctobuf, 4); |
397 | 0 | ft.cto = (byte) (get_le32(ctobuf) >> 24); |
398 | 0 | } |
399 | 4 | ft.unfilter(obuf, ph.u_len - data->size); |
400 | 4 | } |
401 | | |
402 | 18 | if (ph.version < 14) { |
403 | | // fixup for the aligning bug in strip 2.8+ |
404 | 0 | text->scnptr &= 0x1ff; |
405 | 0 | data->scnptr = text->scnptr + text->size; |
406 | | // write decompressed file |
407 | 0 | if (fo) |
408 | 0 | fo->write(obuf, ph.u_len); |
409 | 18 | } else { |
410 | | // write the header |
411 | | // some padding might be required between the end |
412 | | // of the header and the start of the .text section |
413 | | |
414 | 18 | const unsigned padding = text->scnptr - hdrsize; |
415 | 18 | ibuf.clear(0, padding); |
416 | | |
417 | 18 | if (fo) { |
418 | 2 | fo->write(obuf, hdrsize); |
419 | 2 | fo->write(ibuf, padding); |
420 | 2 | fo->write(obuf + hdrsize, ph.u_len - hdrsize); |
421 | 2 | } |
422 | 18 | } |
423 | | |
424 | 18 | if (fo) |
425 | 2 | handle_allegropak(fi, fo); |
426 | 18 | } |
427 | | |
428 | | /* vim:set ts=4 sw=4 et: */ |