; EXED.ASM - Win32 CUI
; Copyright (C) 2023 Boo Khan Ming
;
; MIT license apply
;
; v0.01 - Initial release
; v0.02 - Support for PE32+ (bug fixes for "Code section not found" error for 64-bit EXE)
; v0.03 - Renamed from EXEDUMP to EXED.
;         Output omitted file offset and ASCII characters.
; v0.04 - Fix command-line parsing for PowerShell
;
format PE console
entry start

include 'win32a.inc'

section '.data' readable writable

_message0 db 'Usage: EXEDUMP <executable filename>',13,10,0
_msglen0  = $ - _message0
_message1 db 'INVALID_HANDLE_VALUE',13,10,0
_msglen1  = $ - _message1
_message2 db 'ReadFile FALSE',13,10,0
_msglen2  = $ - _message2
_message3 db 'Invalid EXE',13,10,0
_msglen3  = $ - _message3
_message4 db 'Invalid PE',13,10,0
_msglen4  = $ - _message4
_message5 db 'Only 32-bit (x86) PE is supported.',13,10,0
_msglen5  = $ - _message5
_message6 db 'Only 64-bit (x64) PE is supported.',13,10,0
_msglen6  = $ - _message6
_message7 db 'Code section not found.',13,10,0
_msglen7  = $ - _message7
_dummy    dd ?
_short    db ?
          db 0
_double   dw ?
_hexnum   rb 8
_hexval   rb 2
_digits   db '0123456789ABCDEF'
_space1   db 32,32
_len1     = $ - _space1
_space2   db 32
_len2     = $ - _space2
_filename rb MAX_PATH
_fnlen    dd ?
_buffer   rb 16
_len      dd ?
_ptr      dd ?
_handle   dd ?
_stdout   dd ?
_count    dd ?
_offset   dd 0
_pe_start dd ?
_pe_id_r  rb 4
_pe_id    db 'P','E',0,0
_mz_id_r  rb 2
_mz_id    db 'M','Z'
_machine  dw ?
_section  dw ?
_baseofcode dd ?
_fileptr  dd ?
_magic    dw ?
_virtualaddress   dd ?
_sizeofrawdata    dd ?
_pointertorawdata dd ?
_sectiontable     rb 40

IMAGE_FILE_MACHINE_I386 = 0x014c
IMAGE_FILE_MACHINE_AMD64 = 0x8664
PE32 = 0x10b
PE64 = 0x20b

OffsetToBaseOfCode = 0xac - 0x80
OffsetToImageBase = 0xb0 - 0x80
OffsetToSectionTableForPE32 = 0x178 - 0x80
OffsetToSectionTableForPE64 = 0x178 - 0x80 + 16
OffsetToMagicNumber = 0x98 - 0x80

section '.code' code readable executable

start:
        call    [GetCommandLine]
        push    eax
        cld
        mov     edi, eax
        or      ecx, -1
        xor     eax, eax
        repnz   scasb           ; Calculate total length of command line arguments
        not     ecx
        pop     eax
        push    eax
        mov     dword [_fnlen], ecx
        mov     edi, eax
        or      ecx, -1
        mov     eax, 32
        repnz   scasb           ; Find the first white space after first argument
        repz    scasb           ; Scan for extra white spacing between first argument and second argument
        not     ecx
        mov     dword [_dummy], ecx
        sub     dword [_fnlen], ecx
        cmp     dword [_fnlen], 0    ; Check if no argument is supplied
        jle      .err0
        mov     ecx, dword [_fnlen]
        pop     eax
        add     eax, dword [_dummy]
        dec     eax
        mov     esi, eax
        lea     edi, [_filename]
        rep     movsb                ;invoke  GetStdHandle, -11
        ;invoke  WriteConsole, eax, _filename, dword [_fnlen], _dummy, 0
        invoke  CreateFile, _filename, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0
        mov     dword [_handle], eax
        cmp     eax, INVALID_HANDLE_VALUE
        je      .err1
        invoke  GetStdHandle, -11
        mov     dword [_stdout],eax

        invoke  ReadFile, dword [_handle], _mz_id_r, 2, _len, 0
        test    eax, eax
        jz      .err2
        movzx   ebx, word [_mz_id_r]
        cmp     bx, word [_mz_id]
        jnz     .err3
        invoke  SetFilePointer, dword [_handle], 0x3C, 0, FILE_BEGIN
        invoke  ReadFile, dword [_handle], _pe_start, 4, _len, 0
        test    eax, eax
        jz      .err2
        invoke  SetFilePointer, dword [_handle], dword [_pe_start], 0, FILE_BEGIN
        invoke  ReadFile, dword [_handle], _pe_id_r, 4, _len, 0
        test    eax, eax
        jz      .err2
        mov     ebx, dword [_pe_id_r]
        cmp     ebx, dword [_pe_id]
        jnz     .err4
        invoke  ReadFile, dword [_handle], _machine, 2, _len, 0
        test    eax, eax
        jz      .err2
        movzx   ebx, word [_machine]
        ;Uncomment below to perform check on x86_32 or x64 code
        ;cmp     ebx, IMAGE_FILE_MACHINE_I386
        ;jnz     .err5
        ;cmp     ebx, IMAGE_FILE_MACHINE_AMD64
        ;jnz     .err6
        invoke  ReadFile, dword [_handle], _section, 2, _len, 0
        test    eax, eax
        jz      .err2

        mov     ebx, dword [_pe_start]
        add     ebx, OffsetToMagicNumber
        mov     [_fileptr], ebx
        invoke  SetFilePointer, dword [_handle], dword [_fileptr] , 0, FILE_BEGIN
        invoke  ReadFile, dword [_handle], _magic, 2, _len, 0
        test    eax, eax
        jz      .err2

        mov     ebx, dword [_pe_start]
        add     ebx, OffsetToBaseOfCode
        mov     [_fileptr], ebx
        invoke  SetFilePointer, dword [_handle], dword [_fileptr] , 0, FILE_BEGIN
        invoke  ReadFile, dword [_handle], _baseofcode, 4, _len, 0
        test    eax, eax
        jz      .err2

        movzx   ebx, word [_magic]
        cmp     ebx, PE32
        jz      .isPE32

        mov     ebx, dword [_pe_start]
        add     ebx, OffsetToSectionTableForPE64
        mov     [_fileptr], ebx
        invoke  SetFilePointer, dword [_handle], dword [_fileptr] , 0, FILE_BEGIN
        jmp     .continue
.isPE32:
        mov     ebx, dword [_pe_start]
        add     ebx, OffsetToSectionTableForPE32
        mov     [_fileptr], ebx
        invoke  SetFilePointer, dword [_handle], dword [_fileptr] , 0, FILE_BEGIN

.continue:
        movzx   ecx, word [_section]
.scan:     ;loop through all section table until match found for code section
        push    ecx
        invoke  ReadFile, dword [_handle], _sectiontable, 40, _len, 0
        test    eax, eax
        jz      .err2
        pop     ecx
        mov     ebx, dword [_sectiontable + 12]
        mov     [_virtualaddress], ebx
        mov     ebx, dword [_sectiontable + 16]
        mov     [_sizeofrawdata], ebx
        mov     ebx, dword [_sectiontable + 20]
        mov     [_pointertorawdata], ebx
        mov     ebx, dword [_baseofcode]
        cmp     ebx, dword [_virtualaddress]
        jz      .donescan
        dec     ecx
        jnz     .scan
        jmp     .err7
.donescan:
        invoke  SetFilePointer, dword [_handle], dword [_pointertorawdata] , 0, FILE_BEGIN

.redo:
        invoke  ReadFile, dword [_handle], _buffer, 16, _len, 0
        test    eax, eax
        jz      .err2
        mov     ecx, dword [_len]
        test    ecx, ecx
        jz      .close
        mov     [_count], ecx

        mov     edx, [_offset]
        cmp     edx, dword [_sizeofrawdata]
        jae     .close
        mov     ecx, 8
        call    ConvertLongHex
        ;call    PrintOffset
        ;call    PrintLongSpace
        xor     ecx, ecx
      
.repeat1:
        xor     edx, edx
        mov     dl, byte [_buffer + ecx]
        push    ecx
        mov     ecx, 2
        call    ConvertShortHex
        call    PrintHex
        call    PrintShortSpace
        pop     ecx
        inc     ecx
        cmp     ecx, [_count]
        jb      .repeat1
        cmp     ecx, 16
        jb      .pad1
        jmp     .skip1
.pad1:
        mov     ebx, 16
        sub     ebx,ecx
.pad3:
        call    PrintShortSpace
        call    PrintShortSpace
        call    PrintShortSpace
        dec     ebx
        jnz     .pad3
.skip1:
        call    PrintLongSpace
        xor     ecx, ecx
      
.repeat2:
        xor     edx, edx
        mov     dl, byte [_buffer + ecx]
        push    ecx
        cmp     dl, 32
        jb      .dot
        cmp     dl, 127
        jae     .dot
        mov     [_short], dl
        ;call    PrintChar
        jmp     .skip2
.dot:
        mov     [_short], '.'
        ;call    PrintChar
.skip2:
        pop     ecx
        inc     ecx
        cmp     ecx, [_count]
        jb      .repeat2
        cmp     ecx, 16
        jb      .pad2
        jmp     .skip3
.pad2:
        mov     ebx, 16
        sub     ebx, ecx
.pad4:
        call    PrintShortSpace
        call    PrintShortSpace
        dec     ebx
        jnz     .pad4
.skip3:
        call    PrintLine
        add     [_offset], 16
        jmp     .redo

.close:
        invoke  CloseHandle, [_handle]
        jmp     .done

.err0:
        lea     edx, [_message0]
        mov     ecx, _msglen0
        jmp     .error

.err1:
        lea     edx, [_message1]
        mov     ecx, _msglen1
        jmp     .error

.err2:
        lea     edx, [_message2]
        mov     ecx, _msglen2
        jmp     .error

.err3:
        lea     edx, [_message3]
        mov     ecx, _msglen3
        jmp     .error

.err4:
        lea     edx, [_message4]
        mov     ecx, _msglen4
        jmp     .error

.err5:
        lea     edx, [_message5]
        mov     ecx, _msglen5
        jmp     .error

.err6:
        lea     edx, [_message6]
        mov     ecx, _msglen6
        jmp     .error

.err7:
        lea     edx, [_message7]
        mov     ecx, _msglen7
        jmp     .error

.error:
        invoke  GetStdHandle, -11
        invoke  WriteConsole, eax, edx, ecx, _dummy, 0

.done:
        invoke  ExitProcess,0

PrintLongSpace:
        mov     edx, _len1
        lea     esi, [_space1]
        call    Print
        ret
PrintShortSpace:
        mov     edx, _len2
        lea     esi, [_space2]
        call    Print
        ret
PrintLine:
        mov     edx, 2
        mov     [_double], 0x0A0D
        lea     esi, [_double]
        call    Print
        ret
PrintOffset:
        mov     edx, 8
        lea     esi, [_hexnum]
        call    Print
        ret
PrintHex:
        mov     edx, 2
        lea     esi, [_hexval]
        call    Print
        ret
PrintChar:      
        mov     edx, 1
        lea     esi, [_short]
        call    Print
        ret
Print:
        ;invoke  WriteConsole, dword [_stdout], esi, edx, _dummy, 0
        invoke  WriteFile, dword [_stdout], esi, edx, _dummy, 0
        ret
            
ConvertLongHex:    ;-) Nice code snippet by Tomasz Grysztar (flat assembler)
        xor      ebx,ebx
.loop1:
        rol      edx,4
        mov      eax,edx
        and      eax,1111b
        mov      al,[_digits+eax]
        mov      [ebx+_hexnum],al
        inc      ebx
        dec      ecx
        jnz      .loop1
        ret
      
ConvertShortHex:   ;-) Nice code snippet by Tomasz Grysztar (flat assembler)
        xor      ebx,ebx
.loop2:
        rol      dl,4
        mov      al,dl
        and      eax,1111b
        mov      al,[_digits+eax]
        mov      [ebx+_hexval],al
        inc      ebx
        dec      ecx
        jnz      .loop2
        ret
 
section '.idata' import readable writable

 library kernel32, 'KERNEL32.DLL'

 import kernel32,\
        GetStdHandle, 'GetStdHandle', \
        WriteConsole, 'WriteConsoleA', \
        CreateFile, 'CreateFileA', \
        ReadFile, 'ReadFile', \
        WriteFile, 'WriteFile', \
        CloseHandle, 'CloseHandle', \
        GetCommandLine, 'GetCommandLineA', \
        SetFilePointer, 'SetFilePointer', \
        ExitProcess,'ExitProcess'