Created
April 3, 2020 07:17
-
-
Save kajott/063dfce6a94da03a379553b3db6be334 to your computer and use it in GitHub Desktop.
minimal Win32 application that runs calc.exe (268 bytes)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
; A simple Win32 application that runs the Windows calculator. | |
; | |
; This version constructs the whole EXE file "from scratch" in the | |
; assembler -- no linker needed. | |
; This is a sectionless executable with an "import by hash" loader | |
; in which as many stuff as possible has been collapsed (overlaid) | |
; into the headers. In addition, all cleanup code has been removed, | |
; it's assumed that kernel32.dll is the third loaded image in the | |
; PE loader's list, that the kernel32.dll image is actually valid, | |
; and that all required functions are available. | |
; | |
; assemble with: | |
; yasm -fbin -oruncalc.exe runcalc.asm | |
; to get a 268 byte executable (of which 48 bytes are unused) | |
bits 32 | |
BASE equ 0x00400000 | |
ALIGNMENT equ 4 | |
SECTALIGN equ 4 | |
FILL equ 48 ; fill with NOPs to get to the minimum allowed .exe size | |
%define ROUND(v, a) (((v + a - 1) / a) * a) | |
%define ALIGNED(v) (ROUND(v, ALIGNMENT)) | |
%define RVA(obj) (obj - BASE) | |
org BASE | |
mz_hdr: | |
dw "MZ" ; DOS magic | |
dw "kj" ; filler to align the PE header | |
pe_hdr: | |
dw "PE",0 ; PE magic + 2 padding bytes | |
dw 0x014c ; i386 architecture | |
dw 0 ; no sections | |
N_calc: db "calc.exe",0,0,0,0 ; 12 bytes of data collapsed into the header | |
;dd 0 ; [UNUSED-12] timestamp | |
;dd 0 ; [UNUSED] symbol table pointer | |
;dd 0 ; [UNUSED] symbol count | |
dw 8 ; optional header size | |
dw 0x0102 ; characteristics: 32-bit, executable | |
opt_hdr: | |
dw 0x010b ; optional header magic | |
main_part_1: ; 12 bytes of main entry point + 2 bytes of jump | |
mov eax, [fs:0x30] ; get PEB pointer from TEB | |
mov eax, [eax+0x0C] ; get PEB_LDR_DATA pointer from PEB | |
mov eax, [eax+0x14] ; go to first LDR_DATA_TABLE_ENTRY | |
jmp main_part_2 | |
align 4, db 0 | |
;db 13,37 ; [UNUSED-14] linker version | |
;dd RVA(the_end) ; [UNUSED] code size | |
;dd RVA(the_end) ; [UNUSED] size of initialized data | |
;dd 0 ; [UNUSED] size of uninitialized data | |
dd RVA(main_part_1) ; entry point address | |
main_part_2: ; another 6 bytes of code + 2 bytes of jump | |
; set up stack frame for local variables | |
mov eax, [eax] ; go to where ntdll.dll typically is | |
push 0 ; push parameter for ExitProcess (0) | |
push 10 ; push parameter for WinExec (nShowCmd = SW_SHOWDEFAULT) | |
jmp main_part_3 | |
align 4, db 0 | |
;dd RVA(main) ; [UNUSED-8] base of code | |
;dd RVA(main) ; [UNUSED] base of data | |
dd BASE ; image base | |
dd SECTALIGN ; section alignment (collapsed with the | |
; PE header offset in the DOS header) | |
dd ALIGNMENT ; file alignment | |
main_part_3: ; another 5 bytes of code + 2 bytes of jump | |
mov eax, [eax] ; go to where kernel32.dll typically is | |
mov ebx, [eax+0x10] ; load base address of the library | |
jmp main_part_4 | |
align 4, db 0 | |
;dw 4,0 ; [UNUSED-8] OS version | |
;dw 0,0 ; [UNUSED] image version | |
dw 4,0 ; subsystem version | |
dd 0 ; [UNUSED-4] Win32 version | |
dd RVA(the_end) ; size of image | |
dd RVA(opt_hdr) ; size of headers (must be small enough | |
; so that entry point inside header is accepted) | |
dd 0 ; [UNUSED-4] checksum | |
dw 2 ; subsystem = GUI | |
dw 0 ; [UNUSED-2] DLL characteristics | |
dd 0x00100000 ; maximum stack size | |
dd 0x00001000 ; initial stack size | |
dd 0x00100000 ; maximum heap size | |
dd 0x00001000 ; initial heap size | |
dd 0 ; [UNUSED-4] loader flags | |
dd 0 ; number of data directory entries (= none!) | |
OPT_HDR_SIZE equ $ - opt_hdr | |
ALL_HDR_SIZE equ $ - $$ | |
;;;;;;;;;;;;;;;;;;;; .text ;;;;;;;;;;;;;;;;; | |
main_part_4: | |
push N_calc | |
mov esi, 0x6B7A2FDB ; hash of "WinExec" | |
call call_import ; call WinExec | |
mov esi, 0x665640AC ; hash of "ExitProcess" | |
; fall-through into call_import | |
call_import: ; FUNCTION that calls procedure [esi] in library at base [ebx] | |
mov edx, [ebx+0x3c] ; get PE header pointer (w/ RVA translation) | |
add edx, ebx | |
mov edx, [edx+0x78] ; get export table pointer RVA (w/ RVA translation) | |
add edx, ebx | |
push edx ; store the export table address for later | |
mov ecx, [edx+0x18] ; ecx = number of named functions | |
mov edx, [edx+0x20] ; edx = address-of-names list (w/ RVA translation) | |
add edx, ebx | |
name_loop: | |
push esi ; store the desired function name's hash (we will clobber it) | |
mov edi, [edx] ; load function name (w/ RVA translation) | |
add edi, ebx | |
cmp_loop: | |
movzx eax, byte [edi] ; load a byte of the name ... | |
inc edi ; ... and advance the pointer | |
xor esi, eax ; apply xor-and-rotate | |
rol esi, 7 | |
or eax, eax ; last byte? | |
jnz cmp_loop ; if not, process another byte | |
or esi, esi ; result hash match? | |
jnz next_name ; if not, this is not the correct name | |
; if we arrive here, we have a match! | |
pop esi ; restore the name pointer (though we don't use it any longer) | |
pop edx ; restore the export table address | |
sub ecx, [edx+0x18] ; turn the negative counter ECX into a positive one | |
neg ecx | |
mov eax, [edx+0x24] ; get address of ordinal table (w/ RVA translation) | |
add eax, ebx | |
movzx ecx, word [eax+ecx*2] ; load ordinal from table | |
;sub ecx, [edx+0x10] ; subtract ordinal base | |
mov eax, [edx+0x1C] ; get address of function address table (w/ RVA translation) | |
add eax, ebx | |
mov eax, [eax+ecx*4] ; load function address (w/ RVA translation) | |
add eax, ebx | |
jmp eax ; jump to the target function | |
next_name: | |
pop esi ; restore the name pointer | |
add edx, 4 ; advance to next list item | |
dec ecx ; decrease counter | |
jmp name_loop | |
times FILL nop | |
align ALIGNMENT, db 0 | |
the_end: |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
unemployment final boss