mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-01-31 03:27:39 +00:00
396 lines
16 KiB
C
396 lines
16 KiB
C
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
|
||
│ vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi │
|
||
╞══════════════════════════════════════════════════════════════════════════════╡
|
||
│ Copyright 2020 Justine Alexandra Roberts Tunney │
|
||
│ │
|
||
│ Permission to use, copy, modify, and/or distribute this software for │
|
||
│ any purpose with or without fee is hereby granted, provided that the │
|
||
│ above copyright notice and this permission notice appear in all copies. │
|
||
│ │
|
||
│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │
|
||
│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │
|
||
│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │
|
||
│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │
|
||
│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │
|
||
│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │
|
||
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
|
||
│ PERFORMANCE OF THIS SOFTWARE. │
|
||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||
#include "libc/calls/calls.h"
|
||
#include "libc/calls/struct/stat.h"
|
||
#include "libc/fmt/conv.h"
|
||
#include "libc/fmt/libgen.h"
|
||
#include "libc/intrin/safemacros.h"
|
||
#include "libc/mem/gc.h"
|
||
#include "libc/mem/mem.h"
|
||
#include "libc/nt/struct/imagedosheader.internal.h"
|
||
#include "libc/nt/struct/imagentheaders.internal.h"
|
||
#include "libc/nt/struct/imageoptionalheader.internal.h"
|
||
#include "libc/nt/struct/imagesectionheader.internal.h"
|
||
#include "libc/runtime/runtime.h"
|
||
#include "libc/serialize.h"
|
||
#include "libc/stdio/stdio.h"
|
||
#include "libc/str/str.h"
|
||
#include "libc/sysv/consts/map.h"
|
||
#include "libc/sysv/consts/o.h"
|
||
#include "libc/sysv/consts/prot.h"
|
||
#include "libc/x/xasprintf.h"
|
||
#include "third_party/xed/x86.h"
|
||
#include "tool/decode/lib/asmcodegen.h"
|
||
#include "tool/decode/lib/flagger.h"
|
||
#include "tool/decode/lib/peidnames.h"
|
||
#include "tool/decode/lib/titlegen.h"
|
||
|
||
/**
|
||
* @fileoverview Portable executable metadata disassembler.
|
||
* @see https://www.aldeid.com/wiki/PE-Portable-executable
|
||
*/
|
||
|
||
static const char *path;
|
||
|
||
static struct NtImageDosHeader *mz;
|
||
static size_t mzsize;
|
||
|
||
static struct NtImageSectionHeader *sections;
|
||
static size_t section_count;
|
||
|
||
static void *GetOff(uint32_t off) {
|
||
if (off < mzsize)
|
||
return (char *)mz + off;
|
||
fprintf(stderr, "%s: off %#x not defined within image\n", path, off);
|
||
exit(1);
|
||
}
|
||
|
||
static void *GetRva(uint32_t rva) {
|
||
int i;
|
||
for (i = 0; i < section_count; ++i) {
|
||
if (sections[i].VirtualAddress <= rva &&
|
||
rva < sections[i].VirtualAddress + sections[i].Misc.VirtualSize) {
|
||
return (char *)mz + sections[i].PointerToRawData +
|
||
(rva - sections[i].VirtualAddress);
|
||
}
|
||
}
|
||
fprintf(stderr, "%s: rva %#x not defined by any sections\n", path, rva);
|
||
exit(1);
|
||
}
|
||
|
||
static struct XedDecodedInst *ildreal(void *addr) {
|
||
static struct XedDecodedInst xedd;
|
||
if (xed_instruction_length_decode(
|
||
xed_decoded_inst_zero_set_mode(&xedd, XED_MACHINE_MODE_REAL), addr,
|
||
XED_MAX_INSTRUCTION_BYTES) != XED_ERROR_NONE ||
|
||
!xedd.length) {
|
||
xedd.length = 1;
|
||
}
|
||
return &xedd;
|
||
}
|
||
|
||
static void startfile(void) {
|
||
showtitle("αcτµαlly pδrταblε εxεcµταblε", "tool/decode/pe", NULL, NULL,
|
||
&kModelineAsm);
|
||
printf("#include \"libc/nt/pedef.internal.h\"\n\n", path);
|
||
}
|
||
|
||
static void *pecheckaddress(struct NtImageDosHeader *mz, size_t mzsize,
|
||
void *addr, uint32_t addrsize) {
|
||
if ((intptr_t)addr < (intptr_t)mz ||
|
||
(intptr_t)addr + addrsize > (intptr_t)mz + mzsize) {
|
||
abort();
|
||
}
|
||
return addr;
|
||
}
|
||
|
||
static void showmzheader(void) {
|
||
showtitle(basename(gc(strdup(path))), "dos", "mz header",
|
||
"\tMZ = Mark 'Zibo' Joseph Zbikowski\n"
|
||
"\te_cblp: bytes on last page\n"
|
||
"\te_cp: 512-byte pages in file\n"
|
||
"\te_crlc: reloc table entry count\n"
|
||
"\te_cparhdr: data segment file offset / 16\n"
|
||
"\te_{min,max}alloc: lowers upper bound load / 16\n"
|
||
"\te_ss: lower bound on stack segment\n"
|
||
"\te_sp: initialize stack pointer\n"
|
||
"\te_csum: ∑bₙ checksum don't bother\n"
|
||
"\te_ip: initial ip value\n"
|
||
"\te_cs: increases cs load lower bound\n"
|
||
"\te_lfarlc: reloc table offset\n"
|
||
"\te_ovno: overlay number\n"
|
||
"\te_lfanew: portable executable header rva",
|
||
NULL);
|
||
printf("\n");
|
||
show(".ascii", format(b1, "%`'.*s", 2, (const char *)&mz->e_magic),
|
||
"mz->e_magic");
|
||
showshorthex(mz->e_cblp);
|
||
showshorthex(mz->e_cp);
|
||
showshorthex(mz->e_crlc);
|
||
showshorthex(mz->e_cparhdr);
|
||
showshorthex(mz->e_minalloc);
|
||
showshorthex(mz->e_maxalloc);
|
||
showshorthex(mz->e_ss);
|
||
showshorthex(mz->e_sp);
|
||
showshorthex(mz->e_csum);
|
||
showshorthex(mz->e_ip);
|
||
showshorthex(mz->e_cs);
|
||
showshorthex(mz->e_lfarlc);
|
||
showshorthex(mz->e_ovno);
|
||
show(".short",
|
||
format(b1, "%hu,%hu,%hu,%hu", mz->e_res[0], mz->e_res[1], mz->e_res[2],
|
||
mz->e_res[3]),
|
||
"mz->e_res");
|
||
showshorthex(mz->e_oemid);
|
||
showshorthex(mz->e_oeminfo);
|
||
show(".short",
|
||
format(b1, "%hu,%hu,%hu,%hu,%hu,%hu,%hu,%hu,%hu,%hu", mz->e_res2[0],
|
||
mz->e_res2[1], mz->e_res2[2], mz->e_res2[3], mz->e_res2[4],
|
||
mz->e_res2[5], mz->e_res2[6], mz->e_res2[7], mz->e_res2[8],
|
||
mz->e_res2[9]),
|
||
"mz->e_res2");
|
||
showinthex(mz->e_lfanew);
|
||
printf("\n");
|
||
}
|
||
|
||
static void showdosstub(void) {
|
||
}
|
||
|
||
static void showpeoptionalheader(struct NtImageOptionalHeader *opt) {
|
||
int i;
|
||
showtitle(basename(gc(strdup(path))), "windows", "pe \"optional\" header",
|
||
NULL, NULL);
|
||
printf("\n");
|
||
show(".short",
|
||
firstnonnull(findnamebyid(kNtPeOptionalHeaderMagicNames, opt->Magic),
|
||
format(b1, "%#hx", opt->Magic)),
|
||
"opt->Magic");
|
||
showint(opt->MajorLinkerVersion);
|
||
showint(opt->MinorLinkerVersion);
|
||
showinthex(opt->SizeOfCode);
|
||
showinthex(opt->SizeOfInitializedData);
|
||
showinthex(opt->SizeOfUninitializedData);
|
||
showinthex(opt->AddressOfEntryPoint);
|
||
showinthex(opt->BaseOfCode);
|
||
showint64hex(opt->ImageBase);
|
||
showinthex(opt->SectionAlignment);
|
||
showinthex(opt->FileAlignment);
|
||
showshort(opt->MajorOperatingSystemVersion);
|
||
showshort(opt->MinorOperatingSystemVersion);
|
||
showshort(opt->MajorImageVersion);
|
||
showshort(opt->MinorImageVersion);
|
||
showshort(opt->MajorSubsystemVersion);
|
||
showshort(opt->MinorSubsystemVersion);
|
||
showint(opt->Win32VersionValue);
|
||
showinthex(opt->SizeOfImage);
|
||
showinthex(opt->SizeOfHeaders);
|
||
showinthex(opt->CheckSum);
|
||
show(".short",
|
||
firstnonnull(findnamebyid(kNtImageSubsystemNames, opt->Subsystem),
|
||
format(b1, "%#hx", opt->Subsystem)),
|
||
"opt->Subsystem");
|
||
show(".short",
|
||
firstnonnull(RecreateFlags(kNtImageDllcharacteristicNames,
|
||
opt->DllCharacteristics),
|
||
format(b1, "%#hx", opt->DllCharacteristics)),
|
||
"opt->DllCharacteristics");
|
||
showint64hex(opt->SizeOfStackReserve);
|
||
showint64hex(opt->SizeOfStackCommit);
|
||
showint64hex(opt->SizeOfHeapReserve);
|
||
showint64hex(opt->SizeOfHeapCommit);
|
||
showinthex(opt->LoaderFlags);
|
||
showinthex(opt->NumberOfRvaAndSizes);
|
||
|
||
i = 0;
|
||
#define DATA_DIRECTORY(x) \
|
||
do { \
|
||
if (i++ < opt->NumberOfRvaAndSizes) { \
|
||
show(".long", \
|
||
format(b1, "%#X,%u", opt->DataDirectory[x].VirtualAddress, \
|
||
opt->DataDirectory[x].Size), \
|
||
gc(xasprintf("opt->DataDirectory[%s]", #x))); \
|
||
} \
|
||
} while (0)
|
||
|
||
DATA_DIRECTORY(kNtImageDirectoryEntryExport);
|
||
DATA_DIRECTORY(kNtImageDirectoryEntryImport);
|
||
DATA_DIRECTORY(kNtImageDirectoryEntryResource);
|
||
DATA_DIRECTORY(kNtImageDirectoryEntryException);
|
||
DATA_DIRECTORY(kNtImageDirectoryEntrySecurity);
|
||
DATA_DIRECTORY(kNtImageDirectoryEntryBasereloc);
|
||
DATA_DIRECTORY(kNtImageDirectoryEntryDebug);
|
||
DATA_DIRECTORY(kNtImageDirectoryEntryArchitecture);
|
||
DATA_DIRECTORY(kNtImageDirectoryEntryGlobalptr);
|
||
DATA_DIRECTORY(kNtImageDirectoryEntryTls);
|
||
DATA_DIRECTORY(kNtImageDirectoryEntryLoadConfig);
|
||
DATA_DIRECTORY(kNtImageDirectoryEntryBoundImport);
|
||
DATA_DIRECTORY(kNtImageDirectoryEntryIat);
|
||
DATA_DIRECTORY(kNtImageDirectoryEntryDelayImport);
|
||
DATA_DIRECTORY(kNtImageDirectoryEntryComDescriptor);
|
||
|
||
for (; i < opt->NumberOfRvaAndSizes; ++i) {
|
||
showint64hex(opt->DataDirectory[i]);
|
||
}
|
||
}
|
||
|
||
static void ShowIlt(uint32_t rva) {
|
||
int64_t *ilt, *ilt0;
|
||
ilt = ilt0 = GetRva(rva);
|
||
do {
|
||
printf("\n");
|
||
show(".quad", format(b1, "%#lx", *ilt),
|
||
gc(xasprintf("rva=%#lx off=%#lx", (char *)ilt - (char *)ilt0 + rva,
|
||
(intptr_t)ilt - (intptr_t)mz)));
|
||
if (*ilt) {
|
||
char *hint = GetRva(*ilt);
|
||
printf("/\t.short\t%d\t\t\t# @%#lx\n", READ16LE(hint),
|
||
(intptr_t)hint - (intptr_t)mz);
|
||
char *name = GetRva(*ilt + 2);
|
||
printf("/\t.asciz\t%`'s\n", name);
|
||
printf("/\t.align\t2\n");
|
||
}
|
||
} while (*ilt++);
|
||
}
|
||
|
||
static void ShowIdt(char *idt, size_t size) {
|
||
char *p, *e;
|
||
printf("\n");
|
||
showtitle(basename(gc(strdup(path))), "windows",
|
||
"import descriptor table (idt)", 0, 0);
|
||
for (p = idt, e = idt + size; p + 20 <= e; p += 20) {
|
||
printf("\n");
|
||
show(".long", format(b1, "%#x", READ32LE(p)),
|
||
gc(xasprintf("ImportLookupTable RVA @%#lx",
|
||
(intptr_t)p - (intptr_t)mz)));
|
||
show(".long", format(b1, "%#x", READ32LE(p + 4)), "TimeDateStamp");
|
||
show(".long", format(b1, "%#x", READ32LE(p + 8)), "ForwarderChain");
|
||
show(".long", format(b1, "%#x", READ32LE(p + 12)),
|
||
READ32LE(p + 12)
|
||
? gc(xasprintf("DllName RVA (%s)", GetRva(READ32LE(p + 12))))
|
||
: "DllName RVA");
|
||
show(".long", format(b1, "%#x", READ32LE(p + 16)),
|
||
"ImportAddressTable RVA");
|
||
}
|
||
for (p = idt, e = idt + size; p + 20 <= e; p += 20) {
|
||
if (READ32LE(p)) {
|
||
printf("\n");
|
||
showtitle(basename(gc(strdup(path))), "windows",
|
||
"import lookup table (ilt)", 0, 0);
|
||
ShowIlt(READ32LE(p));
|
||
}
|
||
}
|
||
for (p = idt, e = idt + size; p + 20 <= e; p += 20) {
|
||
if (READ32LE(p)) {
|
||
printf("\n");
|
||
showtitle(basename(gc(strdup(path))), "windows",
|
||
"import address table (iat)", 0, 0);
|
||
ShowIlt(READ32LE(p + 16));
|
||
}
|
||
}
|
||
}
|
||
|
||
static void ShowSection(struct NtImageSectionHeader *s) {
|
||
char name[9] = {0};
|
||
memcpy(name, s->Name, 8);
|
||
printf("\n");
|
||
printf("\t.ascin\t\"%'s\",8\n", name);
|
||
printf("\t.long\t%#x\t\t# VirtualSize\n", s->Misc.VirtualSize);
|
||
printf("\t.long\t%#x\t\t# VirtualAddress\n", s->VirtualAddress);
|
||
printf("\t.long\t%#x\t\t# SizeOfRawData\n", s->SizeOfRawData);
|
||
printf("\t.long\t%#x\t\t# PointerToRawData\n", s->PointerToRawData);
|
||
printf("\t.long\t%#x\t\t# PointerToRelocations\n", s->PointerToRelocations);
|
||
printf("\t.long\t%#x\t\t# PointerToLinenumbers\n", s->PointerToLinenumbers);
|
||
printf("\t.short\t%#x\t\t# NumberOfRelocations\n", s->NumberOfRelocations);
|
||
printf("\t.short\t%#x\t\t# NumberOfLinenumbers\n", s->NumberOfLinenumbers);
|
||
|
||
printf("\
|
||
// ┌31:Writeable ┌─────────────────────────┐\n\
|
||
// │┌30:Readable │ PE Section Flags │\n\
|
||
// ││┌29:Executable ├─────────────────────────┤\n\
|
||
// │││┌28:Shareable │ o │ for object files │\n\
|
||
// ││││┌27:Unpageable │ r │ reserved │\n\
|
||
// │││││┌26:Uncacheable └───┴─────────────────────┘\n\
|
||
// ││││││┌25:Discardable\n\
|
||
// │││││││┌24:Contains Extended Relocations\n\
|
||
// ││││││││ ┌15:Contains Global Pointer (GP) Relative Data\n\
|
||
// ││││││││ │ ┌7:Contains Uninitialized Data\n\
|
||
// ││││││││ │ │┌6:Contains Initialized Data\n\
|
||
// ││││││││ o │ ││┌5:Contains Code\n\
|
||
// ││││││││┌┴─┐rrrr│ ooror│││rorrr\n\
|
||
\t.long\t0b%.32b\t\t# Characteristics\n",
|
||
s->Characteristics);
|
||
}
|
||
|
||
static void ShowSections(struct NtImageSectionHeader *s, size_t n) {
|
||
size_t i;
|
||
sections = s;
|
||
section_count = n;
|
||
printf("\n");
|
||
showtitle(basename(gc(strdup(path))), "windows", "sections", 0, 0);
|
||
for (i = 0; i < n; ++i) {
|
||
ShowSection(s + i);
|
||
}
|
||
}
|
||
|
||
static void showpeheader(struct NtImageNtHeaders *pe) {
|
||
showtitle(basename(gc(strdup(path))), "windows", "pe header", NULL, NULL);
|
||
printf("\n");
|
||
showorg(mz->e_lfanew);
|
||
show(".ascii", format(b1, "%`'.*s", 4, (const char *)&pe->Signature),
|
||
"pe->Signature");
|
||
show(".short",
|
||
firstnonnull(
|
||
findnamebyid(kNtImageFileMachineNames, pe->FileHeader.Machine),
|
||
format(b1, "%#hx", pe->FileHeader.Machine)),
|
||
"pe->FileHeader.Machine");
|
||
showshort(pe->FileHeader.NumberOfSections);
|
||
showinthex(pe->FileHeader.TimeDateStamp);
|
||
showinthex(pe->FileHeader.PointerToSymbolTable);
|
||
showint(pe->FileHeader.NumberOfSymbols);
|
||
showshort(pe->FileHeader.SizeOfOptionalHeader);
|
||
show(".short",
|
||
firstnonnull(RecreateFlags(kNtImageCharacteristicNames,
|
||
pe->FileHeader.Characteristics),
|
||
format(b1, "%#hx", pe->FileHeader.Characteristics)),
|
||
"pe->FileHeader.Characteristics");
|
||
printf("\n");
|
||
showpeoptionalheader(pecheckaddress(mz, mzsize, &pe->OptionalHeader,
|
||
pe->FileHeader.SizeOfOptionalHeader));
|
||
ShowSections(pecheckaddress(mz, mzsize,
|
||
(char *)(pe + 1) +
|
||
pe->OptionalHeader.NumberOfRvaAndSizes * 8,
|
||
pe->FileHeader.NumberOfSections *
|
||
sizeof(struct NtImageSectionHeader)),
|
||
pe->FileHeader.NumberOfSections);
|
||
ShowIdt(GetRva(pe->OptionalHeader.DataDirectory[kNtImageDirectoryEntryImport]
|
||
.VirtualAddress),
|
||
pe->OptionalHeader.DataDirectory[kNtImageDirectoryEntryImport].Size);
|
||
}
|
||
|
||
static void showall(void) {
|
||
|
||
startfile();
|
||
showmzheader();
|
||
showdosstub();
|
||
if (mz->e_lfanew) {
|
||
showpeheader(GetOff(mz->e_lfanew));
|
||
}
|
||
}
|
||
|
||
int main(int argc, char *argv[]) {
|
||
int64_t fd;
|
||
struct stat st[1];
|
||
ShowCrashReports();
|
||
if (argc != 2)
|
||
fprintf(stderr, "usage: %s FILE\n", argv[0]), exit(1);
|
||
if ((fd = open((path = argv[1]), O_RDONLY)) == -1 || fstat(fd, st) == -1 ||
|
||
(mz = mmap(NULL, (mzsize = st->st_size), PROT_READ, MAP_SHARED, fd, 0)) ==
|
||
MAP_FAILED) {
|
||
fprintf(stderr, "error: %'s %m\n", path);
|
||
exit(1);
|
||
}
|
||
if (mz->e_magic != kNtImageDosSignature) {
|
||
fprintf(stderr, "error: %'s not a dos executable\n", path);
|
||
exit(1);
|
||
}
|
||
showall();
|
||
munmap(mz, mzsize);
|
||
close(fd);
|
||
return 0;
|
||
}
|