-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathhspsum.cpp
334 lines (286 loc) · 8.58 KB
/
hspsum.cpp
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
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
// SPDX-License-Identifier: LGPL-3.0-only
/**
* Verify checksum of HSP EXE.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <sys/types.h>
#include <sys/stat.h>
uint8_t file_readbyte(FILE * fp)
{
return fgetc(fp);
}
uint16_t file_readshort(FILE * fp)
{
uint8_t b1 = file_readbyte(fp);
uint8_t b2 = file_readbyte(fp);
return b1 | (b2 << 8);
}
uint32_t file_readword(FILE * fp)
{
uint16_t s1 = file_readshort(fp);
uint16_t s2 = file_readshort(fp);
return s1 | (s2 << 16);
}
void file_writebyte(FILE * fp, uint8_t value)
{
fputc(value, fp);
}
void file_writeshort(FILE * fp, uint16_t value)
{
file_writebyte(fp, value & 0xff);
file_writebyte(fp, (value >> 8) & 0xff);
}
void file_writeword(FILE * fp, uint32_t value)
{
file_writeshort(fp, value & 0xffff);
file_writeshort(fp, (value >> 16) & 0xffff);
}
bool is_portable_executable(const char * filename)
{
// get file size
struct stat fileinf;
if (stat(filename, &fileinf) != 0) {
return false;
}
size_t filesize = fileinf.st_size;
// check exe size (until PE header offset)
if (filesize < 0x40) {
return false;
}
// open the exeutable file
FILE * fp = fopen(filename, "rb");
if (fp == NULL) {
return false;
}
// check MS-DOS executable signature
if (file_readshort(fp) != 0x5a4d) { // "MZ"
fclose(fp);
return false;
}
// get PE (Portable Executable) header offset
fseek(fp, 0x3c, SEEK_SET);
uint32_t offset_pe_header = file_readword(fp);
if (offset_pe_header + 4 > filesize) {
fclose(fp);
return false;
}
// check PE header signature
fseek(fp, offset_pe_header, SEEK_SET);
if (file_readword(fp) != 0x4550) { // "PE\0\0"
fclose(fp);
return false;
}
return true;
}
off_t file_search_bin(FILE * fp, const char * pattern, size_t pattern_size)
{
if (pattern_size == 0) {
return -1;
}
while (feof(fp) == 0) {
int c = fgetc(fp);
if (c != EOF && c == pattern[0]) {
size_t i;
for (i = 1; i < pattern_size; i++) {
c = fgetc(fp);
if (c != EOF && c != pattern[i]) {
break;
}
}
if (i == pattern_size) {
fseek(fp, -pattern_size, SEEK_CUR);
return (off_t) ftell(fp);
}
else {
fseek(fp, i - 1, SEEK_CUR);
}
}
}
return -1;
}
off_t search_dpmx(FILE * fp, size_t filesize)
{
rewind(fp);
off_t offset_to_dpm;
while ((offset_to_dpm = file_search_bin(fp, "DPMX", 4)) != -1) {
// additional check for false-positive case (the string "DPMX" also appears in the runtime part)
uint32_t the_word;
// offset to data section
the_word = file_readword(fp);
if (feof(fp) != 0 || the_word < 0x30) {
fseek(fp, -4 + 1, SEEK_CUR);
continue;
}
// number of files
the_word = file_readword(fp);
if (feof(fp) != 0 || the_word == 0 || the_word >= 0x1000000) {
fseek(fp, -8 + 1, SEEK_CUR);
continue;
}
return offset_to_dpm;
}
return -1;
}
off_t search_hsphed(FILE * fp, size_t filesize, uint32_t offset_to_dpm)
{
rewind(fp);
off_t res = file_search_bin(fp, "\x18\x23\x20\x18\x15\x14\x4e\x4e", 8);
return res != -1 ? res + 9 + 8 : -1;
}
void printUsage (const char * cmd)
{
printf("Usage: %s (options) filename.exe\n", cmd);
printf("\n");
printf("Options\n");
printf("-------\n");
printf("\n");
printf("- `-f`: Fix broken checksum if possible\n");
printf("\n");
}
int main(int argc, char * argv[])
{
bool verbose = false;
bool fix_checksum = false;
bool fix_offset = false;
int argi = 1;
while (argi < argc && argv[argi][0] == '-') {
if (strcmp(argv[argi], "-f") == 0) {
fix_checksum = true;
}
else if (strcmp(argv[argi], "-o") == 0) {
fix_offset = true;
}
else if (strcmp(argv[argi], "-v") == 0 || strcmp(argv[argi], "--verbose") == 0) {
verbose = true;
}
else if (strcmp(argv[argi], "--help") == 0) {
printUsage(argv[0]);
return EXIT_FAILURE;
}
else {
printf("Error: Unknown option \"%s\"\n", argv[argi]);
}
argi++;
}
int argnum = argc - argi;
if (argnum == 0) {
printUsage(argv[0]);
return EXIT_FAILURE;
}
if (argnum > 1) {
printf("Error: Too many arguments\n");
return EXIT_FAILURE;
}
const char * filename = argv[argi];
// check PE (Portable Executable) header
if (!is_portable_executable(filename)) {
printf("Error: Not a Portable Executable image \"%s\"\n", filename);
return EXIT_FAILURE;
}
// get file size
struct stat fileinf;
if (stat(filename, &fileinf) != 0) {
printf("Error: Unable to open \"%s\"\n", filename);
return EXIT_FAILURE;
}
size_t filesize = fileinf.st_size;
// open the exeutable file
FILE * fp = fopen(filename, "rb");
if (fp == NULL) {
printf("Error: Unable to open \"%s\"\n", filename);
return EXIT_FAILURE;
}
// HSP EXE has a DPM archive, usually at the end of executable.
// The offset to DPM archive is hardcoded to the EXE, usually in ".data" section.
// The offset is coded as an ASCII decimal string. (For example, "176128")
// It is a relative offset from the EXE offset 0x10000.
// The region which holds the offset string is sometimes called HSPHED.
// The HSPHED contains the checksum and DPM encryption keys. So we need to find it first.
// search for DPM archive
off_t offset_to_dpm = search_dpmx(fp, filesize);
if (offset_to_dpm == -1) {
printf("Error: Unable to find DPM archive in \"%s\"\n", filename);
fclose(fp);
return EXIT_FAILURE;
}
if (verbose) {
printf("DPM archive offset: 0x%08X\n", offset_to_dpm);
}
// search for HSPHED
off_t offset_to_hsphed = search_hsphed(fp, filesize, offset_to_dpm);
if (offset_to_hsphed == -1) {
printf("Error: Unable to find HSPHED in \"%s\" (maybe compressed?)\n", filename);
fclose(fp);
return EXIT_FAILURE;
}
if (verbose) {
printf("HSPHED offset: 0x%08X\n", offset_to_hsphed);
}
// read expected checksum and key for checksum
fseek(fp, offset_to_hsphed + 0x14, SEEK_SET);
uint16_t checksum_in_header = file_readshort(fp);
fseek(fp, offset_to_hsphed + 0x17, SEEK_SET);
uint32_t crypt_key = file_readword(fp);
// calculate the checksum from the head of DPM to EOF (see dpmread.cpp)
uint16_t checksum = 0;
uint32_t checksum_data_size = 0;
fseek(fp, offset_to_dpm, SEEK_SET);
while (feof(fp) == 0) {
int c = fgetc(fp);
if (c != EOF) {
checksum += c;
checksum_data_size++;
}
}
checksum += (((crypt_key >> 24) & 0xff) / 7) * checksum_data_size;
fclose(fp);
if (checksum_in_header == checksum) {
if (verbose) {
printf("Checksum: 0x%04X\n", checksum);
}
else {
printf("%04x\n", checksum);
}
}
else {
printf("Header Checksum: 0x%04X\n", checksum_in_header);
printf("Actual Checksum: 0x%04X\n", checksum);
if (fix_checksum) {
// open the exeutable file
FILE * fp = fopen(filename, "r+b");
if (fp == NULL) {
printf("Error: Unable to open \"%s\"\n", filename);
return EXIT_FAILURE;
}
if (fseek(fp, offset_to_hsphed + 0x14, SEEK_SET) != 0) {
printf("Error: fseek failed \"%s\"\n", filename);
fclose(fp);
return EXIT_FAILURE;
}
file_writeshort(fp, checksum);
printf("Checksum updated\n");
fclose(fp);
}
}
if (fix_offset) {
FILE *fp = fopen(filename, "r+b");
if (fp == NULL) {
printf("Error: Unable to open \"%s\"\n", filename);
return EXIT_FAILURE;
}
char buf[8] = {0};
snprintf(buf, 8, "%d", offset_to_dpm-0x10000);
if (fseek(fp, offset_to_hsphed, SEEK_SET) != 0) {
printf("Error: fseek failed \"%s\"\n", filename);
fclose(fp);
return EXIT_FAILURE;
}
fwrite(buf, 8, 1, fp);
printf("Offset updated\n");
fclose(fp);
}
return EXIT_SUCCESS;
}