3

I've seen

How to write hello world in assembler under Windows?

and

Writing hello,world to console in Fasm with DOS

How to write to the console in fasm?

I've tried / seen code like this MASM example from this answer

;---ASM Hello World Win64 MessageBox

extrn MessageBoxA: PROC
extrn ExitProcess: PROC

.data
title db 'Win64', 0
msg db 'Hello World!', 0

.code
main proc
  sub rsp, 28h  
  mov rcx, 0       ; hWnd = HWND_DESKTOP
  lea rdx, msg     ; LPCSTR lpText
  lea r8,  title   ; LPCSTR lpCaption
  mov r9d, 0       ; uType = MB_OK
  call MessageBoxA
  add rsp, 28h  
  mov ecx, eax     ; uExitCode = MessageBox(...)
  call ExitProcess
main endp

End

(to which I get an error "Illegal instruction" on windows 64 bit extrn MessageBoxA:PROC because FASM doesn't understand that MASM directive.)

also this FASM example from this question

 ; Example of 64-bit PE program


format PE64 GUI 
entry start 

section '.text' code readable executable 

  start: 
      sub     rsp,8*5         ; reserve stack for API use and make stack dqword aligned 

    mov     r9d,0 
    lea     r8,[_caption] 
    lea     rdx,[_message] 
    mov    rcx,0 
    call    [MessageBoxA] 

    mov     ecx,eax 
    call    [ExitProcess] 

section '.data' data readable writeable 

  _caption db 'Win64 assembly program',0 
  _message db 'Hello World!',0 

section '.idata' import data readable writeable 

  dd 0,0,0,RVA kernel_name,RVA kernel_table 
  dd 0,0,0,RVA user_name,RVA user_table 
  dd 0,0,0,0,0 

  kernel_table: 
    ExitProcess dq RVA _ExitProcess 
    dq 0 
  user_table: 
    MessageBoxA dq RVA _MessageBoxA 
    dq 0 

  kernel_name db 'KERNEL32.DLL',0 
  user_name db 'USER32.DLL',0 

  _ExitProcess dw 0 
    db 'ExitProcess',0 
  _MessageBoxA dw 0 
    db 'MessageBoxA',0

but it displays a message box and also has external dependencies "kernel32.dll" and "user32.dll"

also tried this example from the FASM forum

format pe console


include 'win32ax.inc'

entry main

section '.data!!!' data readable writeable

strHello db 'Hello World !',13,10,0
strPause db 'pause',0

section '.txt' code executable readable

main:
       ; you can use crt functions or windows API.
       cinvoke printf,strHello
       cinvoke system,strPause; or import getc()
       ; or
       ; invoke printf,srtHello
       ; add esp, 4

       ; or use WriteFile and GetStdHandle APIs
       push 0
       call [ExitProcess]
      
section '.blah' import data readable

library kernel32,'kernel32.dll',\
    msvcrt,'msvcrt.dll'    ;; C-Run time from MS. This is always on every windows machine

import kernel32,\
          ExitProcess,'ExitProcess'
import msvcrt,\
          printf,'printf',\
          system,'system'

but it depends on win32ax.inc and other imports

also

format PE console
include 'win32ax.inc'
.code
start:
        invoke  WriteConsole,<invoke GetStdHandle,STD_OUTPUT_HANDLE>,"Hello World !",13,0
        invoke  Sleep,-1
.end start

but requires "win32ax.inc" import

closest I could find without the win32ax from the FASM forum:

format pe64 console
entry start

STD_OUTPUT_HANDLE       = -11

section '.text' code readable executable

start:
        sub     rsp,8*7         ; reserve stack for API use and make stack dqword aligned
        mov     rcx,STD_OUTPUT_HANDLE
        call    [GetStdHandle]
        mov     rcx,rax
        lea     rdx,[message]
        mov     r8d,message_length
        lea     r9,[rsp+4*8]
        mov     qword[rsp+4*8],0
        call    [WriteFile]
        mov     ecx,eax
        call    [ExitProcess]

section '.data' data readable writeable

message         db 'Hello World!',0
message_length  = $ - message

section '.idata' import data readable writeable

        dd      0,0,0,RVA kernel_name,RVA kernel_table
        dd      0,0,0,0,0

kernel_table:
        ExitProcess     dq RVA _ExitProcess
        GetStdHandle    dq RVA _GetStdHandle
        WriteFile       dq RVA _WriteFile
                        dq 0

kernel_name     db 'KERNEL32.DLL',0
user_name       db 'USER32.DLL',0

_ExitProcess    db 0,0,'ExitProcess',0
_GetStdHandle   db 0,0,'GetStdHandle',0
_WriteFile      db 0,0,'WriteFile',0    

but still requires the kernel32.dll and user32.dll

Any way to do this without any external DLLs at all? I know just the program fasm itself does it, and prints to the console, doesn't it?

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
  • 1
    You could use undocumented / unsupported system calls directly via `syscall`, with syscall number and args depending on Windows kernel version. https://j00ru.vexillium.org/syscalls/nt/64/ / [Windows system calls](https://stackoverflow.com/q/21074334). Or maybe there's some supported way to still use stable ABIs via DLLs that you wouldn't count as "external"? – Peter Cordes Aug 13 '20 at 06:04
  • @Peter the syscall idea sounds good, is it completely undocumented though? What can I do to find out how to achieve the same effect as `dd 0,0,0,RVA kernel_name,RVA kernel_table` and the user_name parts? – B''H Bi'ezras -- Boruch Hashem Aug 13 '20 at 06:07
  • 1
    How do you know FASM itself "does it"? Did you use some tool to check the `fasm.exe` to see what DLLs it links against, or did you check its source code and/or build scripts? Yes, the syscall ABI used by the DLLs internally to talk to the kerneel is completely undocumented by Microsoft; read the links I gave you if you don't believe or understand what I just said. – Peter Cordes Aug 13 '20 at 06:17
  • @peter fasm.asm is on GitHub (and can itself be built with fasm.exe, which can then be used to build more versions of fasm, lol – B''H Bi'ezras -- Boruch Hashem Aug 13 '20 at 11:17
  • 3
    I know it's open source, but I haven't read the source myself. Have you? If so, what exactly did you look for to verify that it didn't use any functions from any DLLs? Clearly it *does* use DLL functions as per dxiv's answer, like any sane Windows program. It's not clear if you're just looking for FASM syntax that replicates whatever is in `win32ax.inc` (which you could presumably copy/paste if you wanted) so you can use winapi dll functions as manually as possible, or if you really want to try to build a "static" executable that truly doesn't reference any DLLs (use undocumented syscalls). – Peter Cordes Aug 13 '20 at 11:35
  • 3
    What's your intent here? If you want to get to the lower possible level than, that's it: DLL on Windows and syscalls on Linux/BSDs. If you don't want to have PE imports (for whatever reason) then you can get a reference to NTDLL.DLL from the PEB. This DLL is always loaded (but for picoprocesses). Note that even under Linux/BSD, UI is done by a userspace library, just like in Windows. That's really no much difference between assembly and C when you are given an OS: it's all just calling functions. – Margaret Bloom Aug 13 '20 at 14:01
  • @MargaretBloom: To be fair, Linux does give you a guaranteed-stable kernel ABI so you *can* inline `syscall` into your hand-written asm; it makes a ton of sense to do that if you already do your own I/O buffering instead of using stdio fprintf / fwrite etc. (Cheaper ABI for the caller; only a couple call-clobbered regs.) C just usually chooses not to so wrapper functions can update errno with POSIX semantics, and check for thread cancellation points. And so system calls don't need to be inline-asm wrapper functions, just prototypes. – Peter Cordes Aug 13 '20 at 18:08
  • 1
    @MargaretBloom. Also, until x86-64, the fast way to do system calls (`sysenter`) was not the portable way (`int 0x80`), so not inlining the kernel ABI directly into user-space by default allowed a perf gain when a faster instruction became available. We don't expect that to happen for x86-64, but indirecting through libc leaves that option open. – Peter Cordes Aug 13 '20 at 18:10
  • @PeterCordes I was talking from a human point of view :) Of course, ASM and C differ but for a programmer, the way you use `fwrite` or `sys_write` are very similar. In fact, unless you need to control the exact instructions (for performance, as you said, or for some tricky binary post elaboration you'll do on the file) I think one seldom needs to use assembly (instead of C). – Margaret Bloom Aug 13 '20 at 18:20
  • @MargaretBloom: 100% agreed, I'd never write a whole program in asm. Simply no point in doing so, other than an exercise in proving you can. I see what you mean now, from the POV of a normal person, not the OP's quixotic goal in mind :P – Peter Cordes Aug 13 '20 at 18:52

5 Answers5

5

Any way to do this without any external DLLs at all?

Under Windows: Definitely no!

Windows uses some methods (probably syscall) to enter the operating system, however, there are no official entry points.

This means that it is (unlikely but) possible that exactly the same program that shows the "Hello world" message box in the current Windows version will do something completely different after the next Windows update!

Because Microsoft is assuming that every Windows program is only calling the OS by using the .dll files that match the kernel version, they can do this.

I don't know about Windows 10, but an older Windows version (I don't remember if it was XP, Vista or 7) even simply assumed that an .exe file returns at once if it does not use any .dll file: The program was not even started in this case!

Martin Rosenau
  • 17,897
  • 3
  • 19
  • 38
  • Wow lol what a scam on their part! Thanks for the info though – B''H Bi'ezras -- Boruch Hashem Aug 13 '20 at 11:15
  • 5
    @bluejayke: It's not a "scam", it's just a design decision you don't like. MacOS doesn't fully guarantee stability, but being opern-source it is at least usable and documented. Linux does have a documented stable kernel ABI, probably because of the kernel and C library being developed separately. If you don't like being scammed, use a better OS (and Free software in general). – Peter Cordes Aug 13 '20 at 11:29
  • 3
    It's not a "scam"; it's an undocumented, private API. Every piece of software has this. – Cody Gray - on strike Aug 16 '20 at 06:55
5

I know just the program fasm itself does it, and prints to the console

That is not the case, fasm is also using the kernel32 APIs.

enter image description here

FWIW kernel32 is loaded into the memory space of every process in Windows, so there is no penalty or overhead in using the kernel32 APIs.

dxiv
  • 16,984
  • 2
  • 27
  • 49
  • But is there a way to do it without that, in order to write an assembly from scratch? – B''H Bi'ezras -- Boruch Hashem Aug 13 '20 at 11:14
  • 1
    @bluejayke If you want to do it fully "from scratch", while conforming to the documented API, Windows isn't the OS for you. Do it in Linux, or write for bare metal and run in an emulator. – Thomas Jager Aug 13 '20 at 11:46
  • @thomas is there a way to do it on windows without the documented API? – B''H Bi'ezras -- Boruch Hashem Aug 13 '20 at 13:09
  • @bluejayke As discussed everywhere else here, there is some internal syscall API, but using it risky and undocumented. Is there a reason that you want to do this? – Thomas Jager Aug 13 '20 at 13:14
  • 1
    @bluejayke There is no reliable, portable Windows way to do it, as pointed out already. The question is interesting from a curiosity/hacking angle, but in practical order there also isn't much reason to attempt to do it. By the time your entry point gets control, kernel32 is already loaded and mapped into the process memory space. In fact, you'll find it in the stack that *calls* your entry point. So why not use it since it's there "for free" already. – dxiv Aug 13 '20 at 15:50
  • 1
    @bluejayke: "from scatch" is fully compatible with using DLLs. Knowing how to generate the appropriate DLL import tables or whatever Windows dynamic linking truly needs is not fundamentally different from knowing how to emit the right file format headers to make a binary the kernel will load for you. Exposing that via some syntax for programs written for your hypothetical new assembler is up to you to design syntax. The Linux syscall ABI is based on call numbers that programs can embed manually. On Windows the WinAPI is exposed via symbol *names* so you want an assembler+linker to help. – Peter Cordes Aug 13 '20 at 18:23
3

You may like this Windows example in €ASM, which doesn't explicitly mention any DLL and doesn't require other external libraries.

Just save the source as "bluej.asm", assemble and link with euroasm bluej.asm and run as bluej.exe.

Nevertheless, you won't get away without using API functions imported from the default Windows system library "kernel32.dll".

bluej PROGRAM Format=PE, Entry=Start:
        IMPORT GetStdHandle,WriteFile,ExitProcess
Start:  PUSH -11         ; Param 1: standard output handle identificator.
        CALL GetStdHandle; Return StdOutput handle in EAX.
        PUSH 0           ; Param 5: no overlap.
        PUSH Written     ; Param 4: Address of a variable to store number of written bytes.
        PUSH MsgSize     ; Param 3: Number of bytes to write.
        PUSH Msg         ; Param 2: Address of text.
        PUSH EAX         ; Param 1: Output file handle.
        CALL WriteFile   ; System call.
        PUSH 0           ; Errorlevel.
        CALL ExitProcess ; System call.
Written DD 0
Msg     DB "Hello, world!"
MsgSize EQU $ - Msg
      ENDPROGRAM
vitsoft
  • 5,515
  • 1
  • 18
  • 31
0

What constitures as "dependency" to you? If you want to avoid even operating system DLL's, then you're probably out of luck. You can't rely on syscall numbers alone.

"no dependencies" can also mean "just using existing OS DLL's", such as ntdll, kernel32, etc., but without using 3rd party DLL's that may not be present, such as a specific version of the C runtime.

One method I would like to show is retrieving function pointers from the PEB. This is code that I've written and that I personally use, if I want to have shellcode that has no import section.

PebGetProcAddress works similarly to GetProcAddress, except that the DLL name and function name must be a hash, and the DLL must be loaded by using LoadLibrary.

This may not answer your question exactly, but I hope it gets you somewhat closer to your goal or help others who read it.

PebApi.asm

proc PebGetProcAddress ModuleHash:DWORD, FunctionHash:DWORD
    local   FirstEntry:DWORD
    local   CurrentEntry:DWORD
    local   ModuleBase:DWORD
    local   ExportDirectory:DWORD
    local   NameDirectory:DWORD
    local   NameOrdinalDirectory:DWORD
    local   FunctionCounter:DWORD

    ; Get InMemoryOrderModuleList from PEB
    mov     eax, 3
    shl     eax, 4
    mov     eax, [fs:eax] ; fs:0x30
    mov     eax, [eax + PEB.Ldr]
    mov     eax, [eax + PEB_LDR_DATA.InMemoryOrderModuleList.Flink]
    mov     [FirstEntry], eax
    mov     [CurrentEntry], eax

    ; Find module by hash
.L_module:

    ; Compute hash of case insensitive module name
    xor     edx, edx
    mov     eax, [CurrentEntry]
    movzx   ecx, word[eax + LDR_DATA_TABLE_ENTRY.BaseDllName.Length]
    test    ecx, ecx
    jz      .C_module
    mov     esi, [eax + LDR_DATA_TABLE_ENTRY.BaseDllName.Buffer]
    xor     eax, eax
    cld
.L_module_hash:
    lodsb
    ror     edx, 13
    add     edx, eax
    cmp     al, 'a'
    jl      @f
    sub     edx, 0x20 ; Convert lower case letters to upper case
@@: dec     ecx
    test    ecx, ecx
    jnz     .L_module_hash

    ; Check, if module is found by hash
    cmp     edx, [ModuleHash]
    jne     .C_module

    ; Get module base
    mov     eax, [CurrentEntry]
    mov     eax, [eax + LDR_DATA_TABLE_ENTRY.DllBase]
    mov     [ModuleBase], eax

    ; Get export directory
    mov     eax, [ModuleBase]
    add     eax, [eax + IMAGE_DOS_HEADER.e_lfanew]
    mov     eax, [eax + IMAGE_NT_HEADERS32.OptionalHeader.DataDirectoryExport.VirtualAddress]
    add     eax, [ModuleBase]
    mov     [ExportDirectory], eax

    ; Get name table
    mov     eax, [ExportDirectory]
    mov     eax, [eax + IMAGE_EXPORT_DIRECTORY.AddressOfNames]
    add     eax, [ModuleBase]
    mov     [NameDirectory], eax

    ; Get name ordinal table
    mov     eax, [ExportDirectory]
    mov     eax, [eax + IMAGE_EXPORT_DIRECTORY.AddressOfNameOrdinals]
    add     eax, [ModuleBase]
    mov     [NameOrdinalDirectory], eax

    ; Find function in export directory by hash
    mov     [FunctionCounter], 0
.L_functions:
    mov     eax, [ExportDirectory]
    mov     eax, [eax + IMAGE_EXPORT_DIRECTORY.NumberOfNames]
    cmp     eax, [FunctionCounter]
    je      .E_functions

    ; Compute hash of function name
    xor     edx, edx
    mov     esi, [NameDirectory]
    mov     esi, [esi]
    add     esi, [ModuleBase]
    xor     eax, eax
    cld
.L_function_hash:
    lodsb
    test    al, al
    jz      .E_function_hash
    ror     edx, 13
    add     edx, eax
    jmp     .L_function_hash
.E_function_hash:

    ; Check, if function is found by hash
    cmp     edx, [FunctionHash]
    jne     .C_functions

    ; Return function address
    mov     eax, [ExportDirectory]
    mov     eax, [eax + IMAGE_EXPORT_DIRECTORY.AddressOfFunctions]
    add     eax, [ModuleBase]
    mov     ebx, [NameOrdinalDirectory]
    movzx   ebx, word[ebx]
    lea     eax, [eax + ebx * 4]
    mov     eax, [eax]
    add     eax, [ModuleBase]
    ret

.C_functions:
    add     [NameDirectory], 4
    add     [NameOrdinalDirectory], 2
    inc     [FunctionCounter]
    jmp     .L_functions
.E_functions:

    ; Function not found in module's export table
    xor     eax, eax
    ret

.C_module:
    ; Move to next module, exit loop if CurrentEntry == FirstEntry
    mov     eax, [CurrentEntry]
    mov     eax, [eax + LIST_ENTRY.Flink]
    mov     [CurrentEntry], eax
    cmp     eax, [FirstEntry]
    jne     .L_module

    ; Module not found
    xor     eax, eax
    ret
endp

PebApi.inc

macro pebcall modulehash, functionhash, [arg]
{
    common
    if ~ arg eq
        reverse
        pushd arg
        common
    end if

    stdcall PebGetProcAddress, modulehash, functionhash
    call    eax
}

Example

PEB_User32Dll = 0x63c84283
PEB_MessageBoxW = 0xbc4da2be

; pebcall translates to a call to PebGetProcAddress and the call to the returned function pointer
pebcall PEB_User32Dll, PEB_MessageBoxW, NULL, 'Hello, World!', NULL, MB_OK

How to generate hashes for module names and function names

#define ROTR(value, bits) ((DWORD)(value) >> (bits) | (DWORD)(value) << (32 - (bits)))

DWORD ComputeFunctionHash(LPCSTR str)
{
    DWORD hash = 0;

    while (*str)
    {
        hash = ROTR(hash, 13) + *str++;
    }

    return hash;
}

DWORD ComputeModuleNameHash(LPCSTR str, USHORT length)
{
    DWORD hash = 0;

    for (USHORT i = 0; i < length; i++)
    {
        hash = ROTR(hash, 13) + (str[i] >= 'a' ? str[i] - 0x20 : str[i]);
    }

    return hash;
}
bytecode77
  • 14,163
  • 30
  • 110
  • 141
-2

This may help, it reads the string and print each character until the NULL:

;in data segment, write this
string dw 'hello world!', 0
;in code segment:
mov si, string
call print


print proc near
start:
 push ax   
 push si
   aloop:
    lodsb
    or al, al
    jz stop
    mov ah, 0Eh
    mov bh, 0
    int 10h
    jmp aloop
stop:
 pop si
 pop ax
 ret
   print endp
gg man
  • 1
  • 1
  • 2
    This is using the video ROM-BIOS call to int 10h. While this is how to do it in certain environments it does not fit the question. – ecm May 30 '23 at 18:49
  • When they said "no dependencies", they didn't mean "no OS legacy 16-bit mode environment". They're still trying to make a 64-bit Windows executable. Also, prefer `test al,al`. It `or al,al` does the same thing but less efficiently. [Test whether a register is zero with CMP reg,0 vs OR reg,reg?](https://stackoverflow.com/q/33721204) – Peter Cordes May 30 '23 at 18:59