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