Coverage Report

Created: 2026-07-16 07:01

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/upx/src/p_com.cpp
Line
Count
Source
1
/* p_com.cpp -- dos/com executable format
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_com.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/i086-dos16.com.h"
39
40
/*************************************************************************
41
//
42
**************************************************************************/
43
44
0
Linker *PackCom::newLinker() const { return new ElfLinkerX86(); }
45
46
0
const int *PackCom::getCompressionMethods(int method, int level) const {
47
0
    static const int m_nrv2b[] = {M_NRV2B_LE16, M_END};
48
0
    UNUSED(method);
49
0
    UNUSED(level);
50
0
    return m_nrv2b;
51
0
}
52
53
0
const int *PackCom::getFilters() const {
54
    // see class FilterImpl
55
0
    static const int filters[] = {0x06, 0x03, 0x04, 0x01, 0x05, 0x02, FT_END};
56
0
    return filters;
57
0
}
58
59
/*************************************************************************
60
//
61
**************************************************************************/
62
63
0
tribool PackCom::canPack() {
64
0
    byte buf[128];
65
66
0
    fi->readx(buf, sizeof(buf));
67
0
    if (memcmp(buf, "MZ", 2) == 0 || memcmp(buf, "ZM", 2) == 0) // .exe
68
0
        return false;
69
0
    if (memcmp(buf, "\xff\xff\xff\xff", 4) == 0) // .sys
70
0
        return false;
71
0
    if (!fn_has_ext(fi->getName(), "com")) // query file name
72
0
        return false;
73
0
    checkAlreadyPacked(buf, sizeof(buf));
74
0
    if (file_size < 1024)
75
0
        throwCantPack("file is too small for dos/com");
76
0
    if (file_size > 0xFF00)
77
0
        throwCantPack("file is too large for dos/com");
78
0
    return true;
79
0
}
80
81
/*************************************************************************
82
//
83
**************************************************************************/
84
85
0
void PackCom::addFilter16(int filter_id) {
86
0
    assert(filter_id > 0);
87
0
    assert(isValidFilter(filter_id));
88
89
0
    if (filter_id % 3 == 0) {
90
        // clang-format off
91
0
        addLoader("CALLTR16",
92
0
                  filter_id < 4 ? "CT16SUB0" : "",
93
0
                  filter_id < 4 ? "" : (opt->cpu_x86 == opt->CPU_8086 ? "CT16I086" : "CT16I286,CT16SUB0"),
94
0
                  "CALLTRI2",
95
0
                  getFormat() == UPX_F_DOS_COM ? "CORETURN" : "");
96
        // clang-format on
97
0
    } else {
98
        // clang-format off
99
0
        addLoader(filter_id % 3 == 1 ? "CT16E800" : "CT16E900",
100
0
                 "CALLTRI5",
101
0
                 getFormat() == UPX_F_DOS_COM ? "CT16JEND" : "CT16JUL2",
102
0
                 filter_id < 4 ? "CT16SUB1" : "",
103
0
                 filter_id < 4 ? "" : (opt->cpu_x86 == opt->CPU_8086 ? "CT16I087" : "CT16I287,CT16SUB1"),
104
0
                 "CALLTRI6");
105
        // clang-format on
106
0
    }
107
0
}
108
109
0
void PackCom::buildLoader(const Filter *ft) {
110
0
    initLoader(EM_386, stub_i086_dos16_com, sizeof(stub_i086_dos16_com));
111
    // clang-format off
112
0
    addLoader("COMMAIN1",
113
0
              ph.first_offset_found == 1 ? "COMSBBBP" : "",
114
0
              "COMPSHDI",
115
0
              ft->id ? "COMCALLT" : "",
116
0
              "COMMAIN2,UPX1HEAD,COMCUTPO,NRV2B160",
117
0
              ft->id ? "NRVDDONE" : "NRVDRETU",
118
0
              "NRVDECO1",
119
0
              ph.max_offset_found <= 0xd00 ? "NRVLED00" : "NRVGTD00",
120
0
              "NRVDECO2");
121
    // clang-format on
122
0
    if (ft->id) {
123
0
        assert(ft->calls > 0);
124
0
        addFilter16(ft->id);
125
0
    }
126
0
}
127
128
0
void PackCom::patchLoader(OutputFile *fo, byte *loader, int lsize, unsigned calls) {
129
0
    const int e_len = getLoaderSectionStart("COMCUTPO");
130
0
    const int d_len = lsize - e_len;
131
0
    assert(e_len > 0 && e_len < 128);
132
0
    assert(d_len > 0 && d_len < 256);
133
134
0
    const unsigned upper_end = ph.u_len + ph.overlap_overhead + d_len + 0x100;
135
0
    unsigned stacksize = 0x60;
136
0
    if (upper_end + stacksize > 0xfffe)
137
0
        stacksize = 0x56;
138
0
    if (upper_end + stacksize > 0xfffe)
139
0
        throwCantPack("file is too large for dos/com");
140
141
0
    linker->defineSymbol("calltrick_calls", calls);
142
0
    linker->defineSymbol("sp_limit", upper_end + stacksize);
143
0
    linker->defineSymbol("bytes_to_copy", ph.c_len + lsize);
144
0
    linker->defineSymbol("copy_source", ph.c_len + lsize + 0x100);
145
0
    linker->defineSymbol("copy_destination", upper_end);
146
0
    linker->defineSymbol("neg_e_len", 0 - e_len);
147
0
    linker->defineSymbol("NRV2B160", ph.u_len + ph.overlap_overhead);
148
149
0
    relocateLoader();
150
0
    loader = getLoader();
151
152
    // some day we could use the relocation stuff for patchPackHeader too..
153
0
    patchPackHeader(loader, e_len);
154
    // write loader + compressed file
155
0
    fo->write(loader, e_len);         // entry
156
0
    fo->write(obuf, ph.c_len);        // compressed
157
0
    fo->write(loader + e_len, d_len); // decompressor
158
0
    NO_printf("%-13s: entry        : %8u bytes\n", getName(), e_len);
159
0
    NO_printf("%-13s: compressed   : %8u bytes\n", getName(), ph.c_len);
160
0
    NO_printf("%-13s: decompressor : %8u bytes\n", getName(), d_len);
161
0
}
162
163
/*************************************************************************
164
//
165
**************************************************************************/
166
167
0
void PackCom::pack(OutputFile *fo) {
168
    // read file
169
0
    ibuf.alloc(file_size);
170
0
    obuf.allocForCompression(file_size);
171
0
    fi->seek(0, SEEK_SET);
172
0
    fi->readx(ibuf, file_size);
173
174
    // prepare packheader
175
0
    ph.u_len = file_size;
176
    // prepare filter
177
0
    Filter ft(ph.level);
178
0
    ft.addvalue = getCallTrickOffset();
179
    // compress
180
0
    const unsigned overlap_range = ph.u_len < 0xFE00 - ft.addvalue ? 32 : 0;
181
0
    compressWithFilters(&ft, overlap_range, NULL_cconf);
182
183
0
    const int lsize = getLoaderSize();
184
0
    MemBuffer loader(lsize);
185
0
    memcpy(loader, getLoader(), lsize);
186
187
0
    const unsigned calls = ft.id % 3 ? ft.lastcall - 2 * ft.calls : ft.calls;
188
0
    patchLoader(fo, loader, lsize, calls);
189
190
    // verify
191
0
    verifyOverlappingDecompression();
192
193
    // finally check the compression ratio
194
0
    if (!checkFinalCompressionRatio(fo))
195
0
        throwNotCompressible();
196
0
}
197
198
/*************************************************************************
199
//
200
**************************************************************************/
201
202
18.0k
tribool PackCom::canUnpack() {
203
18.0k
    if (!readPackHeader(128)) // read "ph"
204
17.4k
        return false;
205
634
    if (file_size_u <= ph.c_len)
206
0
        return false;
207
634
    return true;
208
634
}
209
210
/*************************************************************************
211
//
212
**************************************************************************/
213
214
63
void PackCom::unpack(OutputFile *fo) {
215
63
    ibuf.alloc(file_size);
216
63
    obuf.allocForDecompression(ph.u_len);
217
218
    // read whole file
219
63
    fi->seek(0, SEEK_SET);
220
63
    fi->readx(ibuf, file_size);
221
222
    // get compressed data offset
223
63
    unsigned e_len = ph.buf_offset + ph.getPackHeaderSize();
224
63
    if (file_size_u <= e_len + ph.c_len)
225
15
        throwCantUnpack("file damaged");
226
227
    // decompress
228
48
    decompress(ibuf + e_len, obuf);
229
230
    // unfilter
231
48
    Filter ft(ph.level);
232
48
    ft.init(ph.filter, getCallTrickOffset());
233
48
    ft.unfilter(obuf, ph.u_len);
234
235
    // write decompressed file
236
48
    if (fo)
237
0
        fo->write(obuf, ph.u_len);
238
48
}
239
240
/* vim:set ts=4 sw=4 et: */