File: pe32edit.sh

package info (click to toggle)
smartmontools 7.5-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 5,080 kB
  • sloc: cpp: 53,631; ansic: 9,924; sh: 6,493; makefile: 1,017
file content (262 lines) | stat: -rwxr-xr-x 6,994 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
#! /bin/bash
#
# Display or edit PE32 file header fields
#
# Home page of code is: https://www.smartmontools.org
#
# Copyright (C) 2023 Christian Franke
#
# SPDX-License-Identifier: GPL-2.0-or-later
#
# $Id: pe32edit.sh 5597 2024-01-24 10:30:14Z chrfranke $
#

set -e -o pipefail

myname=$0

usage()
{
  cat <<EOF
Display or edit PE32 file header fields

Usage: $myname [-s SUBSYSTEM] [-t SECONDS_OR_FILE] [-v] FILE.exe

  -s SUBSYSTEM        Set Subsystem: 2 = GUI, 3 = Console
  -t SECONDS          Set header timestamp to SECONDS since epoch
  -t FILE             Set header timestamp to last modifcation time of FILE
  -v                  Verbose output
EOF
  exit 1
}

error()
{
  echo "$myname: $*" >&2
  exit 1
}

v=

vecho()
{
  test -z "$v" || echo "$*"
}

# is_num STRING
is_num()
{
  test -n "$1" || return 1
  test "${1//[0-9]/}" = "" || return 1
  return 0
}

# time2str SECONDS
time2str()
{
  # Requires GNU version of 'date'
  date -d "@$1" -u +'%Y-%m-%d %H:%M:%S UTC' 2>/dev/null || echo "?"
}

# Parse options
subsystem=; timefile=
while true; do case $1 in
  -s) shift; is_num "$1" || usage; subsystem=$1 ;;
  -t) shift; test -n "$1" || usage; timefile=$1 ;;
  -v) v=t ;;
  -*) usage ;;
  *)  break ;;
esac; shift; done

test $# = 1 || usage

file=$1
tempfile="$file.tmp"

if [ -n "$subsystem" ]; then case $((subsystem)) in
  [23]) ;;
  *) error "Subsystem must be 2 (GUI) or 3 (Console)" ;;
esac; fi

if [ -n "$timefile" ]; then
  if is_num "$timefile"; then
    timestamp=$timefile
  else
    # Requires GNU version of 'stat'
    timestamp=$(stat -c '%Y' "$timefile") || exit 1
    vecho "Using modification time of $timefile: $timestamp [$(time2str $timestamp)]"
  fi
  test $((timestamp & ~0xffffffff)) = 0 || error "Timestamp out of range: $timestamp"
fi

# Read first 512 bytes for quick access to header
x=$(od -A n -N 512 -t u1 -v -w1 "$file") || exit 1
mapfile header <<<"$x"
test "${#header[*]}" == 512 || error "$file: Invalid file size"

# get_uint16 OFFSET
get_uint16()
{
  test "$1" -lt 510 || error "$file: Invalid file offset: $2"
  echo $((${header[$1]} + (${header[$1+1]} << 8)))
}

# get_uint32 OFFSET
get_uint32()
{
  test "$1" -lt 508 || error "$file: Invalid file offset: $2"
  echo $((${header[$1]} + (${header[$1+1]} << 8) \
       + (${header[$1+2]} << 16)  + (${header[$1+3]} << 24)))
}

# IMAGE_DOS_HEADER.pe_magic == "MZ" ?
x=$(get_uint16 0)
test "$x" = $((0x5a4d)) || error "$file: MS-DOS 'MZ' header not present"
# pehdr_offs = IMAGE_DOS_HEADER.lfa_new
pehdr_offs=$(get_uint32 $((0x3c)))
test $((0x40)) -le "$pehdr_offs" && test "$pehdr_offs" -le $((0x180)) \
|| error "$file: Invalid PE header offset: $pehdr_offs"
# IMAGE_NT_HEADERS(32|64).Signature == "PE" ?
x=$(get_uint16 $pehdr_offs)
test "$x" = $((0x4550)) || error "$file: 'PE' header not found at $pehdr_offs"

timestamp_offs=$((pehdr_offs+0x08)) # IMAGE_NT_HEADERS(32|64).FileHeader.TimeDateStamp
checksum_offs=$((pehdr_offs+0x58))  # IMAGE_NT_HEADERS(32|64).OptionalHeader.CheckSum
subsystem_offs=$((pehdr_offs+0x5c)) # IMAGE_NT_HEADERS(32|64).OptionalHeader.Subsystem

machine=$(get_uint16 $((pehdr_offs+0x04))) # IMAGE_NT_HEADERS(32|64).FileHeader.Machine
magic=$(get_uint16 $((pehdr_offs+0x18)))   # IMAGE_NT_HEADERS(32|64).FileHeader.Magic

print_header()
{
  local txt val
  printf 'PE Header Offset:   0x%08x\n' $pehdr_offs

  case $machine in
    "$((0x014c))") txt="i386" ;;
    "$((0x8664))") txt="amd64" ;;
    *)             txt="*Unknown*" ;;
  esac
  printf 'Machine:                0x%04x [%s]\n' $machine "$txt"

  val=$(get_uint32 $timestamp_offs)
  printf 'TimeDateStamp:      0x%08x (%u) [%s]\n' $val $val "$(time2str $val)"

  val=$(get_uint16 $((pehdr_offs+0x16)))
  txt=
  test $((val & 0x0001)) = 0 || txt+=",relocs_stripped"
  test $((val & 0x0002)) = 0 || txt+=",executable"
  test $((val & 0x0004)) = 0 || txt+=",line_nums_stripped"
  test $((val & 0x0008)) = 0 || txt+=",local_syms_stripped"
  test $((val & 0x0020)) = 0 || txt+=",large_addr_aware"
  test $((val & 0x0100)) = 0 || txt+=",32bit"
  test $((val & 0x0200)) = 0 || txt+=",debug_stripped"
  test $((val & 0x2000)) = 0 || txt+=",dll"
  test $((val & 0xdcd0)) = 0 || txt+=",*other*"
  printf 'Characteristics:        0x%04x [%s]\n' $val "${txt#,}"

  case $magic in
    "$((0x010b))") txt="PE32" ;;
    "$((0x020b))") txt="PE32+" ;;
    *)             txt="*Unknown*" ;;
  esac
  printf 'Magic:                  0x%04x [%s]\n' $magic "$txt"

  printf 'CheckSum:           0x%08x\n' "$(get_uint32 $checksum_offs)"

  val=$(get_uint16 $subsystem_offs)
  case $val in
    2) txt="GUI" ;;
    3) txt="Console" ;;
    *) txt="*Unknown*" ;;
  esac
  printf 'Subsystem:              0x%04x [%s]\n' $val "$txt"

  val=$(get_uint16 $((pehdr_offs+0x5e)))
  txt=
  test $((val & 0x0020)) = 0 || txt+=",high_entropy_va"
  test $((val & 0x0040)) = 0 || txt+=",dynamic_base"
  test $((val & 0x0100)) = 0 || txt+=",nx_compat"
  test $((val & 0x4000)) = 0 || txt+=",cf_guard"
  test $((val & 0x8000)) = 0 || txt+=",ts_aware"
  test $((val & 0x3e9f)) = 0 || txt+=",*other*"
  printf 'DllCharacteristics:     0x%04x [%s]\n' $val "${txt#,}"
}

# Print header if no changes requested
if [ -z "$subsystem$timestamp" ]; then
  echo "$file:"
  print_header
  exit 0
fi

# Check file type
case "$machine:$magic:$(get_uint16 $subsystem_offs)" in
  $((0x014c)):$((0x010b)):[23]) ;; # i386:PE32:GUI/Console
  $((0x8664)):$((0x020b)):[23]) ;; # amd64:PE32+:GUI/Console
  *) error "$file: Unknown image type"
esac

# Prepare checksum update
checksum=$(get_uint32 $checksum_offs)
filesize=$(wc -c <"$file") || exit 1
changed=

# Create tempfile
cp -p "$file" "$tempfile" || exit 1

# put_uint16 OFFSET VALUE
put_uint16()
{
  local sum oldval b0 b1
  test $(($1 & 1)) = 0 || error "$file: Offset is not word aligned: $1"

  # Return if unchanged
  oldval=$(get_uint16 $1)
  test $2 != $oldval || return 0
  changed=t

  # Update checksum if valid
  if [ $checksum -gt $filesize ]; then
    sum=$((checksum - filesize))
    if [ $oldval -lt $sum ]; then
      sum=$((sum - oldval + $2))
    else
      sum=$((sum + (0xffff - oldval) + $2));
    fi
    sum=$(((sum + (sum >> 16)) & 0xffff));
    checksum=$((sum + filesize))
  fi

  # Patch the file
  b0=$(($2 & 0xff))
  b1=$((($2 >> 8) & 0xff))
  printf "$(printf '\\x%02x\\x%02x' $b0 $b1)" \
  | dd bs=2 seek=$(($1 >> 1)) count=1 conv=notrunc of="$tempfile" 2>/dev/null \
  || error "$file: Patch at offset $1 failed"

  # Update local copy
  header[$1]=$b0
  header[$1 + 1]=$b1
}

# put_uint32 OFFSET VALUE
put_uint32()
{
  put_uint16 $1 $(($2 & 0xffff))
  put_uint16 $(($1 + 2)) $((($2 >> 16) & 0xffff))
}

# Update requested header fields
test -z "$subsystem" || put_uint16 $subsystem_offs $subsystem
test -z "$timestamp" || put_uint32 $timestamp_offs $timestamp

if [ -n "$changed" ]; then
  put_uint32 $checksum_offs $checksum
  mv -f "$tempfile" "$file" || exit 1
  vecho "$file: Changed"
else
  rm -f "$tempfile"
  vecho "$file: Unchanged"
fi
test -z "$v" || print_header