1295484Semaste/*-
2295484Semaste * Copyright (c) 2015 Kai Wang
3295484Semaste * All rights reserved.
4295484Semaste *
5295484Semaste * Redistribution and use in source and binary forms, with or without
6295484Semaste * modification, are permitted provided that the following conditions
7295484Semaste * are met:
8295484Semaste * 1. Redistributions of source code must retain the above copyright
9295484Semaste *    notice, this list of conditions and the following disclaimer.
10295484Semaste * 2. Redistributions in binary form must reproduce the above copyright
11295484Semaste *    notice, this list of conditions and the following disclaimer in the
12295484Semaste *    documentation and/or other materials provided with the distribution.
13295484Semaste *
14295484Semaste * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15295484Semaste * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16295484Semaste * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17295484Semaste * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18295484Semaste * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19295484Semaste * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20295484Semaste * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21295484Semaste * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22295484Semaste * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23295484Semaste * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24295484Semaste * SUCH DAMAGE.
25295484Semaste */
26295484Semaste
27295484Semaste#include <sys/param.h>
28295484Semaste#include <sys/types.h>
29295484Semaste#include <assert.h>
30295484Semaste#include <errno.h>
31295484Semaste#include <stdlib.h>
32295484Semaste#include <string.h>
33295484Semaste#include <unistd.h>
34295484Semaste
35295484Semaste#include "_libpe.h"
36295484Semaste
37295484SemasteELFTC_VCSID("$Id: libpe_dos.c 3312 2016-01-10 09:23:51Z kaiwang27 $");
38295484Semaste
39295484Semasteint
40295484Semastelibpe_parse_msdos_header(PE *pe, char *hdr)
41295484Semaste{
42295484Semaste	PE_DosHdr *dh;
43295484Semaste	char coff[sizeof(PE_CoffHdr)];
44295484Semaste	uint32_t pe_magic;
45295484Semaste	int i;
46295484Semaste
47295484Semaste	if ((pe->pe_stub = malloc(sizeof(PE_DosHdr))) == NULL) {
48295484Semaste		errno = ENOMEM;
49295484Semaste		return (-1);
50295484Semaste	}
51295484Semaste	memcpy(pe->pe_stub, hdr, sizeof(PE_DosHdr));
52295484Semaste
53295484Semaste	if ((dh = malloc(sizeof(*dh))) == NULL) {
54295484Semaste		errno = ENOMEM;
55295484Semaste		return (-1);
56295484Semaste	}
57295484Semaste	pe->pe_dh = dh;
58295484Semaste
59295484Semaste	/* Read the conventional MS-DOS EXE header. */
60295484Semaste	memcpy(dh->dh_magic, hdr, 2);
61295484Semaste	hdr += 2;
62295484Semaste	PE_READ16(hdr, dh->dh_lastsize);
63295484Semaste	PE_READ16(hdr, dh->dh_nblock);
64295484Semaste	PE_READ16(hdr, dh->dh_nreloc);
65295484Semaste	PE_READ16(hdr, dh->dh_hdrsize);
66295484Semaste	PE_READ16(hdr, dh->dh_minalloc);
67295484Semaste	PE_READ16(hdr, dh->dh_maxalloc);
68295484Semaste	PE_READ16(hdr, dh->dh_ss);
69295484Semaste	PE_READ16(hdr, dh->dh_sp);
70295484Semaste	PE_READ16(hdr, dh->dh_checksum);
71295484Semaste	PE_READ16(hdr, dh->dh_ip);
72295484Semaste	PE_READ16(hdr, dh->dh_cs);
73295484Semaste	PE_READ16(hdr, dh->dh_relocpos);
74295484Semaste	PE_READ16(hdr, dh->dh_noverlay);
75295484Semaste
76295484Semaste	/* Do not continue if the EXE is not a PE/NE/... (new executable) */
77295484Semaste	if (dh->dh_relocpos != 0x40) {
78295484Semaste		pe->pe_flags |= LIBPE_F_BAD_DOS_HEADER;
79295484Semaste		return (0);
80295484Semaste	}
81295484Semaste
82295484Semaste	for (i = 0; i < 4; i++)
83295484Semaste		PE_READ16(hdr, dh->dh_reserved1[i]);
84295484Semaste	PE_READ16(hdr, dh->dh_oemid);
85295484Semaste	PE_READ16(hdr, dh->dh_oeminfo);
86295484Semaste	for (i = 0; i < 10; i++)
87295484Semaste		PE_READ16(hdr, dh->dh_reserved2[i]);
88295484Semaste	PE_READ32(hdr, dh->dh_lfanew);
89295484Semaste
90295484Semaste	/* Check if the e_lfanew pointer is valid. */
91295484Semaste	if (dh->dh_lfanew > pe->pe_fsize - 4) {
92295484Semaste		pe->pe_flags |= LIBPE_F_BAD_DOS_HEADER;
93295484Semaste		return (0);
94295484Semaste	}
95295484Semaste
96295484Semaste	if (dh->dh_lfanew < sizeof(PE_DosHdr) &&
97295484Semaste	    (pe->pe_flags & LIBPE_F_SPECIAL_FILE)) {
98295484Semaste		pe->pe_flags |= LIBPE_F_BAD_DOS_HEADER;
99295484Semaste		return (0);
100295484Semaste	}
101295484Semaste
102295484Semaste	if (dh->dh_lfanew > sizeof(PE_DosHdr)) {
103295484Semaste		pe->pe_stub_ex = dh->dh_lfanew - sizeof(PE_DosHdr);
104295484Semaste		if (pe->pe_flags & LIBPE_F_SPECIAL_FILE) {
105295484Semaste			/* Read in DOS stub now. */
106295484Semaste			if (libpe_read_msdos_stub(pe) < 0) {
107295484Semaste				pe->pe_flags |= LIBPE_F_BAD_DOS_HEADER;
108295484Semaste				return (0);
109295484Semaste			}
110295484Semaste		}
111295484Semaste	}
112295484Semaste
113295484Semaste	if ((pe->pe_flags & LIBPE_F_SPECIAL_FILE) == 0) {
114295484Semaste		/* Jump to the PE header. */
115295484Semaste		if (lseek(pe->pe_fd, (off_t) dh->dh_lfanew, SEEK_SET) < 0) {
116295484Semaste			pe->pe_flags |= LIBPE_F_BAD_PE_HEADER;
117295484Semaste			return (0);
118295484Semaste		}
119295484Semaste	}
120295484Semaste
121295484Semaste	if (read(pe->pe_fd, &pe_magic, 4) != 4 ||
122295484Semaste	    htole32(pe_magic) != PE_SIGNATURE) {
123295484Semaste		pe->pe_flags |= LIBPE_F_BAD_PE_HEADER;
124295484Semaste		return (0);
125295484Semaste	}
126295484Semaste
127295484Semaste	if (read(pe->pe_fd, coff, sizeof(coff)) != (ssize_t) sizeof(coff)) {
128295484Semaste		pe->pe_flags |= LIBPE_F_BAD_COFF_HEADER;
129295484Semaste		return (0);
130295484Semaste	}
131295484Semaste
132295484Semaste	return (libpe_parse_coff_header(pe, coff));
133295484Semaste}
134295484Semaste
135295484Semasteint
136295484Semastelibpe_read_msdos_stub(PE *pe)
137295484Semaste{
138295484Semaste	void *m;
139295484Semaste
140295484Semaste	assert(pe->pe_stub_ex > 0 &&
141295484Semaste	    (pe->pe_flags & LIBPE_F_LOAD_DOS_STUB) == 0);
142295484Semaste
143295484Semaste	if ((pe->pe_flags & LIBPE_F_SPECIAL_FILE) == 0) {
144295484Semaste		if (lseek(pe->pe_fd, (off_t) sizeof(PE_DosHdr), SEEK_SET) <
145295484Semaste		    0) {
146295484Semaste			errno = EIO;
147295484Semaste			goto fail;
148295484Semaste		}
149295484Semaste	}
150295484Semaste
151295484Semaste	if ((m = realloc(pe->pe_stub, sizeof(PE_DosHdr) + pe->pe_stub_ex)) ==
152295484Semaste	    NULL) {
153295484Semaste		errno = ENOMEM;
154295484Semaste		goto fail;
155295484Semaste	}
156295484Semaste	pe->pe_stub = m;
157295484Semaste
158295484Semaste	if (read(pe->pe_fd, pe->pe_stub + sizeof(PE_DosHdr), pe->pe_stub_ex) !=
159295484Semaste	    (ssize_t) pe->pe_stub_ex) {
160295484Semaste		errno = EIO;
161295484Semaste		goto fail;
162295484Semaste	}
163295484Semaste
164295484Semaste	pe->pe_flags |= LIBPE_F_LOAD_DOS_STUB;
165295484Semaste
166295484Semaste	/* Search for the Rich header embedded just before the PE header. */
167295484Semaste	(void) libpe_parse_rich_header(pe);
168295484Semaste
169295484Semaste	return (0);
170295484Semaste
171295484Semastefail:
172295484Semaste	pe->pe_stub_ex = 0;
173295484Semaste
174295484Semaste	return (-1);
175295484Semaste}
176295484Semaste
177295484Semaste/*
178295484Semaste * The "standard" MS-DOS stub displaying "This program cannot be run in
179295484Semaste * DOS mode".
180295484Semaste */
181295484Semastestatic const char msdos_stub[] = {
182295484Semaste    '\x0e','\x1f','\xba','\x0e','\x00','\xb4','\x09','\xcd',
183295484Semaste    '\x21','\xb8','\x01','\x4c','\xcd','\x21','\x54','\x68',
184295484Semaste    '\x69','\x73','\x20','\x70','\x72','\x6f','\x67','\x72',
185295484Semaste    '\x61','\x6d','\x20','\x63','\x61','\x6e','\x6e','\x6f',
186295484Semaste    '\x74','\x20','\x62','\x65','\x20','\x72','\x75','\x6e',
187295484Semaste    '\x20','\x69','\x6e','\x20','\x44','\x4f','\x53','\x20',
188295484Semaste    '\x6d','\x6f','\x64','\x65','\x2e','\x0d','\x0d','\x0a',
189295484Semaste    '\x24','\x00','\x00','\x00','\x00','\x00','\x00','\x00',
190295484Semaste};
191295484Semaste
192295484Semastestatic void
193295484Semasteinit_dos_header(PE_DosHdr *dh)
194295484Semaste{
195295484Semaste
196295484Semaste	dh->dh_magic[0] = 'M';
197295484Semaste	dh->dh_magic[1] = 'Z';
198295484Semaste	dh->dh_lastsize = 144;
199295484Semaste	dh->dh_nblock = 3;
200295484Semaste	dh->dh_hdrsize = 4;
201295484Semaste	dh->dh_maxalloc = 65535;
202295484Semaste	dh->dh_sp = 184;
203295484Semaste	dh->dh_relocpos = 0x40;
204295484Semaste	dh->dh_lfanew = 0x80;
205295484Semaste}
206295484Semaste
207295484Semasteoff_t
208295484Semastelibpe_write_msdos_stub(PE *pe, off_t off)
209295484Semaste{
210295484Semaste	PE_DosHdr *dh;
211295484Semaste	char tmp[sizeof(PE_DosHdr)], *hdr;
212295484Semaste	off_t d;
213295484Semaste	int i, strip_rich;
214295484Semaste
215295484Semaste	strip_rich = 0;
216295484Semaste
217295484Semaste	if (pe->pe_cmd == PE_C_RDWR) {
218295484Semaste		assert((pe->pe_flags & LIBPE_F_SPECIAL_FILE) == 0);
219295484Semaste
220295484Semaste		if (pe->pe_dh != NULL &&
221295484Semaste		    (pe->pe_flags & PE_F_STRIP_DOS_STUB)) {
222295484Semaste			/*
223295484Semaste			 * If we strip MS-DOS stub, everything after it
224295484Semaste			 * needs rewritten.
225295484Semaste			 */
226295484Semaste			pe->pe_flags |= LIBPE_F_BAD_PE_HEADER;
227295484Semaste			goto done;
228295484Semaste		}
229295484Semaste
230295484Semaste		/*
231295484Semaste		 * lseek(2) to the PE signature if MS-DOS stub is not
232295484Semaste		 * modified.
233295484Semaste		 */
234295484Semaste		if (pe->pe_dh != NULL &&
235295484Semaste		    (pe->pe_flags & LIBPE_F_DIRTY_DOS_HEADER) == 0 &&
236295484Semaste		    (pe->pe_flags & LIBPE_F_BAD_DOS_HEADER) == 0 &&
237295484Semaste		    (pe->pe_flags & PE_F_STRIP_RICH_HEADER) == 0) {
238295484Semaste			if (lseek(pe->pe_fd,
239295484Semaste			    (off_t) (sizeof(PE_DosHdr) + pe->pe_stub_ex),
240295484Semaste			    SEEK_CUR) < 0) {
241295484Semaste				errno = EIO;
242295484Semaste				return (-1);
243295484Semaste			}
244295484Semaste			off = sizeof(PE_DosHdr) + pe->pe_stub_ex;
245295484Semaste			goto done;
246295484Semaste		}
247295484Semaste
248295484Semaste		/* Check if we should strip the Rich header. */
249295484Semaste		if (pe->pe_dh != NULL && pe->pe_stub_app == NULL &&
250295484Semaste		    (pe->pe_flags & LIBPE_F_BAD_DOS_HEADER) == 0 &&
251295484Semaste		    (pe->pe_flags & PE_F_STRIP_RICH_HEADER)) {
252295484Semaste			if ((pe->pe_flags & LIBPE_F_LOAD_DOS_STUB) == 0) {
253295484Semaste				(void) libpe_read_msdos_stub(pe);
254295484Semaste				if (lseek(pe->pe_fd, off, SEEK_SET) < 0) {
255295484Semaste					errno = EIO;
256295484Semaste					return (-1);
257295484Semaste				}
258295484Semaste			}
259295484Semaste			if (pe->pe_rh != NULL) {
260295484Semaste				strip_rich = 1;
261295484Semaste				pe->pe_flags |= LIBPE_F_DIRTY_DOS_HEADER;
262295484Semaste			}
263295484Semaste		}
264295484Semaste
265295484Semaste		/*
266295484Semaste		 * If length of MS-DOS stub will change, Mark the PE
267295484Semaste		 * signature is broken so that the PE signature and the
268295484Semaste		 * headers follow it will be rewritten.
269295484Semaste		 *
270295484Semaste		 * The sections should be loaded now since the stub might
271295484Semaste		 * overwrite the section data.
272295484Semaste		 */
273295484Semaste		if ((pe->pe_flags & LIBPE_F_BAD_DOS_HEADER) ||
274295484Semaste		    (pe->pe_stub_app != NULL && pe->pe_stub_app_sz !=
275295484Semaste			sizeof(PE_DosHdr) + pe->pe_stub_ex) || strip_rich) {
276295484Semaste			if (libpe_load_all_sections(pe) < 0)
277295484Semaste				return (-1);
278295484Semaste			if (lseek(pe->pe_fd, off, SEEK_SET) < 0) {
279295484Semaste				errno = EIO;
280295484Semaste				return (-1);
281295484Semaste			}
282295484Semaste			pe->pe_flags |= LIBPE_F_BAD_PE_HEADER;
283295484Semaste		}
284295484Semaste	}
285295484Semaste
286295484Semaste	if (pe->pe_flags & PE_F_STRIP_DOS_STUB)
287295484Semaste		goto done;
288295484Semaste
289295484Semaste	/* Always use application supplied MS-DOS stub, if exists. */
290295484Semaste	if (pe->pe_stub_app != NULL && pe->pe_stub_app_sz > 0) {
291295484Semaste		if (write(pe->pe_fd, pe->pe_stub_app, pe->pe_stub_app_sz) !=
292295484Semaste		    (ssize_t) pe->pe_stub_app_sz) {
293295484Semaste			errno = EIO;
294295484Semaste			return (-1);
295295484Semaste		}
296295484Semaste		off = pe->pe_stub_app_sz;
297295484Semaste		goto done;
298295484Semaste	}
299295484Semaste
300295484Semaste	/*
301295484Semaste	 * Write MS-DOS header.
302295484Semaste	 */
303295484Semaste
304295484Semaste	if (pe->pe_dh == NULL) {
305295484Semaste		if ((dh = calloc(1, sizeof(PE_DosHdr))) == NULL) {
306295484Semaste			errno = ENOMEM;
307295484Semaste			return (-1);
308295484Semaste		}
309295484Semaste		pe->pe_dh = dh;
310295484Semaste
311295484Semaste		init_dos_header(dh);
312295484Semaste
313295484Semaste		pe->pe_flags |= LIBPE_F_DIRTY_DOS_HEADER;
314295484Semaste	} else
315295484Semaste		dh = pe->pe_dh;
316295484Semaste
317295484Semaste	if (pe->pe_flags & LIBPE_F_BAD_DOS_HEADER)
318295484Semaste		init_dos_header(dh);
319295484Semaste
320295484Semaste	if (strip_rich) {
321295484Semaste		d = pe->pe_rh_start - pe->pe_stub;
322295484Semaste		dh->dh_lfanew = roundup(d, 8);
323295484Semaste	}
324295484Semaste
325295484Semaste	if ((pe->pe_flags & LIBPE_F_DIRTY_DOS_HEADER) ||
326295484Semaste	    (pe->pe_flags & LIBPE_F_BAD_DOS_HEADER)) {
327295484Semaste		memcpy(tmp, dh->dh_magic, 2);
328295484Semaste		hdr = tmp + 2;
329295484Semaste		PE_WRITE16(hdr, dh->dh_lastsize);
330295484Semaste		PE_WRITE16(hdr, dh->dh_nblock);
331295484Semaste		PE_WRITE16(hdr, dh->dh_nreloc);
332295484Semaste		PE_WRITE16(hdr, dh->dh_hdrsize);
333295484Semaste		PE_WRITE16(hdr, dh->dh_minalloc);
334295484Semaste		PE_WRITE16(hdr, dh->dh_maxalloc);
335295484Semaste		PE_WRITE16(hdr, dh->dh_ss);
336295484Semaste		PE_WRITE16(hdr, dh->dh_sp);
337295484Semaste		PE_WRITE16(hdr, dh->dh_checksum);
338295484Semaste		PE_WRITE16(hdr, dh->dh_ip);
339295484Semaste		PE_WRITE16(hdr, dh->dh_cs);
340295484Semaste		PE_WRITE16(hdr, dh->dh_relocpos);
341295484Semaste		PE_WRITE16(hdr, dh->dh_noverlay);
342295484Semaste		for (i = 0; i < 4; i++)
343295484Semaste			PE_WRITE16(hdr, dh->dh_reserved1[i]);
344295484Semaste		PE_WRITE16(hdr, dh->dh_oemid);
345295484Semaste		PE_WRITE16(hdr, dh->dh_oeminfo);
346295484Semaste		for (i = 0; i < 10; i++)
347295484Semaste			PE_WRITE16(hdr, dh->dh_reserved2[i]);
348295484Semaste		PE_WRITE32(hdr, dh->dh_lfanew);
349295484Semaste
350295484Semaste		if (write(pe->pe_fd, tmp, sizeof(tmp)) !=
351295484Semaste		    (ssize_t) sizeof(tmp)) {
352295484Semaste			errno = EIO;
353295484Semaste			return (-1);
354295484Semaste		}
355295484Semaste	} else {
356295484Semaste		assert((pe->pe_flags & LIBPE_F_SPECIAL_FILE) == 0);
357295484Semaste		if (lseek(pe->pe_fd, (off_t) sizeof(PE_DosHdr), SEEK_CUR) <
358295484Semaste		    0) {
359295484Semaste			errno = EIO;
360295484Semaste			return (-1);
361295484Semaste		}
362295484Semaste	}
363295484Semaste
364295484Semaste	off = sizeof(PE_DosHdr);
365295484Semaste
366295484Semaste	/*
367295484Semaste	 * Write the MS-DOS stub.
368295484Semaste	 */
369295484Semaste
370295484Semaste	if (strip_rich) {
371295484Semaste		assert((pe->pe_flags & LIBPE_F_SPECIAL_FILE) == 0);
372295484Semaste		assert(pe->pe_stub != NULL && pe->pe_rh_start != NULL);
373295484Semaste		d = pe->pe_rh_start - pe->pe_stub;
374295484Semaste		if (lseek(pe->pe_fd, d, SEEK_SET) < 0) {
375295484Semaste			errno = EIO;
376295484Semaste			return (-1);
377295484Semaste		}
378295484Semaste		off = d;
379295484Semaste		goto done;
380295484Semaste	}
381295484Semaste
382295484Semaste	if (pe->pe_cmd == PE_C_RDWR) {
383295484Semaste		if (lseek(pe->pe_fd, (off_t) pe->pe_stub_ex, SEEK_CUR) < 0) {
384295484Semaste			errno = EIO;
385295484Semaste			return (-1);
386295484Semaste		}
387295484Semaste		off += pe->pe_stub_ex;
388295484Semaste		goto done;
389295484Semaste	}
390295484Semaste
391295484Semaste	if (write(pe->pe_fd, msdos_stub, sizeof(msdos_stub)) !=
392295484Semaste	    (ssize_t) sizeof(msdos_stub)) {
393295484Semaste		errno = EIO;
394295484Semaste		return (-1);
395295484Semaste	}
396295484Semaste	off += sizeof(msdos_stub);
397295484Semaste
398295484Semastedone:
399295484Semaste	pe->pe_flags &= ~LIBPE_F_DIRTY_DOS_HEADER;
400295484Semaste	pe->pe_flags &= ~LIBPE_F_BAD_DOS_HEADER;
401295484Semaste
402295484Semaste	return (off);
403295484Semaste}
404