【问题标题】:FASM write Hello World to console with NO includes or dependencies at allFASM 将 Hello World 写入控制台,完全没有包含或依赖项
【发布时间】:2020-12-02 22:20:09
【问题描述】:

我见过

How to write hello world in assembler under Windows?

Writing hello,world to console in Fasm with DOS

How to write to the console in fasm?

我已经从this answer 尝试/看到了类似这个 MASM 示例的代码

;---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

(我在 Windows 64 位 extrn MessageBoxA:PROC 上收到错误“非法指令”,因为 FASM 不理解该 MASM 指令。)

还有这个 FASM 示例 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

但它会显示一个消息框,并且还具有外部依赖项“kernel32.dll”和“user32.dll”

也试过这个例子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'

但这取决于win32ax.inc 和其他导入

还有

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

但需要“win32ax.inc”导入

没有 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    

但仍需要 kernel32.dll 和 user32.dll

没有任何外部 DLL 的任何方法都可以做到这一点?我只知道程序 fasm 本身会执行此操作,并打印到控制台,不是吗?

【问题讨论】:

  • 您可以通过syscall 直接使用未记录/不受支持的系统调用,系统调用号和参数取决于 Windows 内核版本。 j00ru.vexillium.org/syscalls/nt/64 / Windows system calls。或者,也许有一些受支持的方式仍然可以通过 DLL 使用稳定的 ABI,但您不会算作“外部”?
  • @Peter 系统调用的想法听起来不错,但它完全没有记录吗?我该怎么做才能找到与dd 0,0,0,RVA kernel_name,RVA kernel_table和user_name部分相同的效果?
  • 您如何知道 FASM 本身“会”?您是否使用了一些工具来检查fasm.exe 以查看它链接的 DLL,或者您是否检查了它的源代码和/或构建脚本?是的,DLL 内部用于与内核通信的系统调用 ABI 完全没有被 Microsoft 记录。如果您不相信或不理解我刚才所说的话,请阅读我给您的链接。
  • 我知道它是开源的,但我自己没有阅读源代码。你?如果是这样,您究竟在寻找什么来验证它没有使用任何 DLL 中的任何函数?显然,它确实按照 dxiv 的回答使用 DLL 函数,就像任何理智的 Windows 程序一样。目前尚不清楚您是否只是在寻找复制 win32ax.inc 中任何内容的 FASM 语法(如果您愿意,您可以复制/粘贴)以便您可以尽可能手动使用 winapi dll 函数,或者如果您真的想要尝试构建一个真正不引用任何 DLL 的“静态”可执行文件(使用未记录的系统调用)。
  • 你在这里的意图是什么?如果你想达到更低的级别,那就是:Windows 上的 DLL 和 Linux/BSD 上的系统调用。如果您不想导入 PE(无论出于何种原因),那么您可以从 PEB 获得对 NTDLL.DLL 的引用。始终加载此 DLL(但对于 picoprocesses)。请注意,即使在 Linux/BSD 下,UI 也是由用户空间库完成的,就像在 Windows 中一样。当你得到一个操作系统时,汇编和 C 之间并没有太大区别:它们都只是调用函数。

标签: windows assembly x86-64 system-calls fasm


【解决方案1】:

对您而言,什么构成“依赖”?如果您甚至想避免操作系统 DLL,那么您可能不走运。您不能只依赖系统调用号。

“无依赖关系” 也可以表示“仅使用现有的 OS DLL”,例如 ntdll、kernel32 等,但不使用可能不存在的 3rd 方 DLL,例如 特定的 C 运行时版本。

我想展示的一种方法是从 PEB 中检索函数指针。如果我想要没有导入部分的 shellcode,这是我编写并个人使用的代码。

PebGetProcAddressGetProcAddress 的工作方式类似,不同之处在于 DLL 名称和函数名称必须是哈希,并且必须使用 LoadLibrary 加载 DLL。

这可能无法准确地回答您的问题,但我希望它能让您更接近您的目标或帮助其他阅读它的人。

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
}

示例

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

如何为模块名和函数名生成哈希

#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;
}

【讨论】:

    【解决方案2】:

    您可能会喜欢€ASM 中的这个 Windows 示例,它没有明确提及任何 DLL,也不需要其他外部库。

    只需将源代码保存为“bluej.asm”,组装并链接到euroasm bluej.asm 并以bluej.exe 运行。

    不过,如果不使用从默认 Windows 系统库“kernel32.dll”导入的 API 函数,您将无法逃脱。

    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
    

    【讨论】:

      【解决方案3】:

      我知道只有程序 fasm 自己做,并打印到控制台

      事实并非如此,fasm 也在使用 kernel32 API。

      FWIW kernel32 被加载到 Windows 中每个进程的内存空间中,因此使用 kernel32 API 没有任何损失或开销。

      【讨论】:

      • 但是有没有办法做到这一点,以便从头开始编写程序集?
      • @bluejayke 如果您想完全“从头开始”,同时符合文档化的 API,Windows 不适合您。在 Linux 中执行,或者为裸机编写并在模拟器中运行。
      • @thomas 有没有办法在没有记录 API 的情况下在 Windows 上执行此操作?
      • @bluejayke 正如已经指出的那样,没有可靠、可移植的 Windows 方法来做到这一点。从好奇/黑客的角度来看,这个问题很有趣,但在实际操作中,也没有太多理由尝试这样做。当您的入口点获得控制权时,kernel32 已经加载并映射到进程内存空间。事实上,您会在调用您的入口点的堆栈中找到它。那么为什么不使用它,因为它已经“免费”在那里了。
      • @bluejayke:“from scatch”与使用 DLL 完全兼容。知道如何生成适当的 DLL 导入表或任何 Windows 动态链接真正需要的东西,与知道如何发出正确的文件格式头以生成内核将为您加载的二进制文件没有根本的不同。通过为您假设的新汇编程序编写的程序的某些语法来公开它取决于您设计语法。 Linux 系统调用 ABI 基于程序可以手动嵌入的调用号。在 Windows 上,WinAPI 通过符号 names 公开,因此您需要一个汇编器+链接器来提供帮助。
      【解决方案4】:

      没有任何外部 DLL 的任何方法都可以做到这一点?

      在 Windows 下:绝对不行!

      Windows使用一些方法(可能是syscall)进入操作系统,但是没有官方入口。

      这意味着,在当前 Windows 版本中显示 "Hello world" 消息框的完全相同的程序有可能(不太可能但)在下一次 Windows 更新后执行完全不同的操作!

      因为 Microsoft 假设每个 Windows 程序仅通过使用与内核版本匹配的 .dll 文件来调用操作系统,所以他们可以这样做。

      我不了解 Windows 10,但较旧的 Windows 版本(我不记得是 XP、Vista 还是 7)甚至简单地假设 .exe 文件如果不使用任何文件就会立即返回.dll file: 在这种情况下甚至没有启动程序!

      【讨论】:

      • 哇,哈哈,他们真是个骗局!谢谢你的信息
      • @bluejayke:这不是一个“骗局”,它只是一个你不喜欢的设计决定。 MacOS 并不能完全保证稳定性,但作为 opern-source,它至少是可用的并且有文档记录。 Linux 确实有一个记录在案的稳定内核 ABI,可能是因为内核和 C 库是分开开发的。如果您不喜欢被骗,请使用更好的操作系统(以及一般的免费软件)。
      • 这不是“骗局”;它是一个未记录的私有 API。每个软件都有这个。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-04-17
      • 2016-08-31
      • 1970-01-01
      • 1970-01-01
      • 2011-11-27
      • 2017-05-29
      相关资源
      最近更新 更多