Asm CALL指令 - 它是如何工作的?

Joh*_*ing 4 c c++ windows x86 assembly

我想在Windows环境(PE可执行文件)中有一个明确的解释,CALL XXXXXXXXXXXXXXX指令如何工作.我一直在研究PE格式,但我对CALL ADDRESS指令与从dll导入函数之间的关系以及CALL ADDRESS如何在DLL中找到代码感到困惑.除了ASLR和其他安全功能可能会移动DLL,可执行文件如何处理这个?

har*_*old 9

它(即直接调用具有正常相对调用的导入)不起作用,这就是为什么不是这样做的原因.

要调用导入的函数,请执行称为导入地址表(IAT)的操作.简而言之,IAT中的条目首先指向函数名称(即它从"导入名称表"的副本开始),并且这些指针被更改为指向加载器的实际函数.

IAT位于固定地址,但是如果图像已被重新定位则可以重新定位,因此通过它调用只涉及单个间接 - 因此call r/m与内存操作数(这只是一个简单的常量)一起使用来调用导入的函数,例子call [0x40206C].


Che*_*Alf 6

2013 年 1 月 22 日:添加了更多更简单的具体示例和讨论,因为 (A) 已选择错误答案作为解决方案,以及 (B) 一些读者(包括 OP)显然不理解我的原始答案。对不起,我的过错。我只是匆忙发布了一个答案,添加了一个我手头已有的代码示例。


我如何解释这个问题。

你问,

“我一直在研究 PE 格式,但我对 CALL ADDRESS 指令、从 dll 中导入函数以及 CALL ADDRESS 如何访问 DLL 中的代码之间的关系感到非常困惑。”

术语 CALL ADDRESS 在 C++ 级别没有多大意义,所以我假设您的意思是汇编语言或机器代码级别的 CALL ADDRESS。

那么问题是,当 DLL 加载到首选地址以外的某个地址时,call指令如何连接到 DLL 函数?


不足之处。

  • 在机器代码级别,call具有指定地址的 a 通过调用由单个jmp指令组成的最小转发例程来工作。该jmp指令通过查表调用 DLL 函数。通常,DLL 的导入库会导出带有__imp__名称前缀的 DLL 函数本身和不带此类名称前缀的包装例程,例如__imp__MessageBoxA@16_MessageBoxA@16

即,除了我发明了下面的名称外,汇编程序通常会翻译

    call MessageBox

进入

    call MessageBox_forwarder
     ; 不管在这里
    MessageBox_forwarder: jmp ds:[MessageBox_tableEntry]

当加载 DLL 时,加载器将相关地址放在表中。

  • 在汇编语言级别call,将例程指定为仅标识符的 a 可以映射到call转发器,或call通过表查找直接映射到DLL 函数,具体取决于为标识符声明的类型。

  • 可以有多个 DLL 函数地址表,即使是从同一个 DLL 导入也是如此。但总的来说,它们被认为是一张大表,然后称为“导入地址表”,或简称IAT。IAT 表(或更准确地说是表)每个都位于图像中的固定位置,即当代码加载到不喜欢的地方而不是固定地址时,它们会与代码一起移动。

当前选择的解决方案的答案不正确在以下方面:

  • 答案认为“它不起作用,这就是为什么它不是这样完成的。”,其中“它”大概是指呼叫地址。但是在汇编或机器代码级别使用 CALL ADDRESS 可以很好地调用 DLL 函数。前提是操作正确。

  • 答案是 IAT 位于固定地址。但事实并非如此。


呼叫地址工作得很好。

让我们考虑一个具体的CALL ADDRESS 指令,其中地址是一个众所周知的 DLL 函数,即MessageBoxA从 [user32.dll] DLL调用Windows API 函数:

call MessageBoxA
Run Code Online (Sandbox Code Playgroud)

使用这个指令没有问题。

正如您将在下面看到的,在机器代码级别,该call指令本身仅包含一个偏移量,该偏移量导致调用执行jmp指令,该指令在函数指针的导入地址表中查找 DLL 例程地址,该地址通常由加载程序时加载有问题的 DLL。

为了能够检查机器代码,这里有一个完整的 32 位 x86 汇编语言程序,使用该具体示例指令:

.model flat, stdcall
option casemap :none        ; Case sensitive identifiers, please.
_as32bit        textequ <DWord ptr>

public  start

ExitProcess                     proto stdcall :DWord

MessageBoxA_t                   typedef proto stdcall :DWord, :DWord, :DWord, :DWord
extern MessageBoxA              : MessageBoxA_t
extern _imp__MessageBoxA@16     : ptr MessageBoxA_t

MB_ICONINFORMATION      equ     0040h
MB_SETFOREGROUND        equ     00010000h
infoBoxOptions          equ     MB_ICONINFORMATION or MB_SETFOREGROUND

    .const
boxtitle_1  db  "Just FYI 1 (of 3):", 0
boxtitle_2  db  "Just FYI 2 (of 3):", 0
boxtitle_3  db  "Just FYI 3 (of 3):", 0
boxtext     db  "There’s intelligence somewhere in the universe", 0

    .code
start:
    push infoBoxOptions
    push offset boxtitle_1
    push offset boxtext
    push 0
    call MessageBoxA                    ; Call #1 - to jmp to DLL-func.

    push infoBoxOptions
    push offset boxtitle_2
    push offset boxtext
    push 0
    call ds:[_imp__MessageBoxA@16]      ; Call #2 - directly to DLL-func.

    push infoBoxOptions
    push offset boxtitle_3
    push offset boxtext
    push 0
    call _imp__MessageBoxA@16           ; Call #3 - same as #2, due to type of identifier.

    push 0  ; Exit code, 0 indicates success.
    call ExitProcess
end
Run Code Online (Sandbox Code Playgroud)

使用 Microsoft 的工具链进行组装和链接,其中/debug链接器选项要求链接器生成用于 Visual Studio 调试器的 PDB 调试信息文件:

[d:\dev\test\call]
> ml /nologo /c asm_call.asm
 组装:asm_call.asm

[d:\dev\test\call]
>链接 /nologo asm_call.obj kernel32.lib user32.lib /entry:start /subsystem:windows /debug

[d:\dev\test\call]
>目录 asm* /b
asm_call.asm
asm_call.exe
asm_call.ilk
asm_call.obj
asm_call.pdb

[d:\dev\test\call]
> _

一种简单的调试方法是启动 Visual Studio([devenv.exe] 程序),然后在 Visual Studio 中单击 [ DebugStep into ],或者只需按 F11:

[d:\dev\test\call]
> devenv asm_call.exe

[d:\dev\test\call]
> _

在此处输入图片说明

在上图中,显示了运行中的 Visual Studio 2012 调试器,最左边的红色大箭头显示了机器代码指令中的地址信息,即0000004E十六进制(注意:最低有效字节在最低地址,在内存中的第一个),以及另一个大红色箭头向您表明,尽管看起来令人难以置信,但这个相当小的幻数以某种方式指定了_MessageBoxA@16函数,据调试器所知,该函数位于地址01161064h十六进制。

  • CALL ADDRESS 指令中的地址数据是一个偏移量,它是相对于下一条指令的地址的,因此它不需要对更改的 DLL 位置进行任何修正。

  • 调用的地址只包含一个jmp ds:[IAT_entry_for_MessageBoxA].

  • 这个转发器代码来自导入库,而不是来自 DLL,所以它也不需要修复(但显然它确实得到了一些特殊处理,DLL 函数地址也是如此)。

第二个调用指令直接jmp执行第一个调用指令,即在 IAT 表中查找 DLL 函数地址。

现在可以看到第三个调用指令在机器代码级别与第二个相同。显然,如何在程序declspec( dllimport )集中模拟 Visual C++ 并不为人所知。上述类型的声明是一种方式,可能与文本 equ 结合使用。


IAT 不在固定地址。

下面的 C++ 程序报告了它被加载的地址,它从哪些模块导入了哪些 DLL 函数,以及各种 IAT 表所在的位置。

当它使用现代版本的 Microsoft 工具链构建时,仅使用默认值,它通常在每次运行时加载到不同的地址。

您可以通过使用链接器选项来防止这种行为/dynamicbase:no

#include <assert.h>         // assert
#include <stddef.h>         // ptrdiff_t
#include <sstream>
using std::ostringstream;

#undef UNICODE
#define UNICODE
#include <windows.h>

template< class Result, class SomeType >
Result as( SomeType const p ) { return reinterpret_cast<Result>( p ); }

template< class Type >
class OffsetTo
{
private:
    ptrdiff_t offset_;
public:
    ptrdiff_t asInteger() const { return offset_; }
    explicit OffsetTo( ptrdiff_t const offset ): offset_( offset ) {}
};

template< class ResultPointee, class SourcePointee >
ResultPointee* operator+(
    SourcePointee* const            p,
    OffsetTo<ResultPointee> const   offset
    )
{
    return as<ResultPointee*>( as<char const*>( p ) + offset.asInteger() );
}

int main()
{
    auto const pImage =
        as<IMAGE_DOS_HEADER const*>( ::GetModuleHandle( nullptr ) );
    assert( pImage->e_magic == IMAGE_DOS_SIGNATURE );

    auto const pNTHeaders =
        pImage + OffsetTo<IMAGE_NT_HEADERS const>( pImage->e_lfanew );
    assert( pNTHeaders->Signature == IMAGE_NT_SIGNATURE );

    auto const& importDir =
        pNTHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];

    auto const pImportDescriptors = pImage + OffsetTo<IMAGE_IMPORT_DESCRIPTOR const>(
        importDir.VirtualAddress //+ importSectionHeader.PointerToRawData
        );

    ostringstream stream;
    stream << "I'm loaded at " << pImage << ", and I'm using...\n";
    for( int i = 0;  pImportDescriptors[i].Name != 0;  ++i )
    {
        auto const pModuleName = pImage + OffsetTo<char const>( pImportDescriptors[i].Name );

        DWORD const offsetNameTable = pImportDescriptors[i].OriginalFirstThunk;
        DWORD const offsetAddressTable = pImportDescriptors[i].FirstThunk;  // The module "IAT"

        auto const pNameTable = pImage + OffsetTo<IMAGE_THUNK_DATA const>( offsetNameTable );
        auto const pAddressTable = pImage + OffsetTo<IMAGE_THUNK_DATA const>( offsetAddressTable );

        stream << "\n* '" << pModuleName << "'";
        stream << " with IAT at " << pAddressTable << "\n";
        stream << "\t";
        for( int j = 0; pNameTable[j].u1.AddressOfData != 0; ++j )
        {
            auto const pFuncName =
                pImage + OffsetTo<char const>( 2 + pNameTable[j].u1.AddressOfData );
            stream << pFuncName << " ";
        }
        stream << "\n";
    }

    MessageBoxA(
        0,
        stream.str().c_str(),
        "FYI:",
        MB_ICONINFORMATION | MB_SETFOREGROUND
        );
}
Run Code Online (Sandbox Code Playgroud)

在此处输入图片说明


一个自我复制的 Windows 机器代码程序。

最后,根据我的原始答案,这是我为另一个目的而制作的 Microsoft 汇编程序 (MASM) 程序,它说明了一些问题,因为就其性质而言(它作为输出源代码生成,在组装和运行时会生成相同的源代码,并且等等)它必须是完全可重定位的代码,并且只需要来自普通程序加载器的最少帮助:

.model flat, stdcall
option casemap :none        ; Case sensitive identifiers, please.
dword_aligned textequ <4>   ; Just for readability.

    ; Windows API functions:
    extern  ExitProcess@4: proc         ; from [kernel32.dll]
    extern  GetStdHandle@4: proc        ; from [kernel32.dll]
    extern  WriteFile@20: proc          ; from [kernel32.dll]
    extern  wsprintfA: proc             ; from [user32.dll]

    STD_OUTPUT_HANDLE       equ     -11

        ; The main code.
GlobalsStruct   struct  dword_aligned
    codeStart               dword   ?
    outputStreamHandle      dword   ?
GlobalsStruct   ends
globals         textequ     <(GlobalsStruct ptr [edi])>

    .code
startup:
    jmp     code_start

    ; Trampolines to add references to these functions.
myExitProcess:    jmp ExitProcess@4
myGetStdHandle:   jmp GetStdHandle@4     
myWriteFile:      jmp WriteFile@20
mywsprintfA:      jmp wsprintfA


;------------------------------------------------------------------
;
;               The code below is reproduced, so it's all relative.

code_start:
    jmp     main

prologue:
byte    ".model flat, stdcall", 13, 10
byte    "option casemap :none", 13, 10
byte    13, 10
byte    "    extern  ExitProcess@4: proc", 13, 10
byte    "    extern  GetStdHandle@4: proc", 13, 10
byte    "    extern  WriteFile@20: proc", 13, 10
byte    "    extern  wsprintfA: proc", 13, 10
byte    13, 10
byte    "    .code", 13, 10
byte    "startup:", 13, 10
byte    "    jmp     code_start", 13, 10
byte    13, 10
byte    "jmp ExitProcess@4", 13, 10
byte    "jmp GetStdHandle@4", 13, 10
byte    "jmp WriteFile@20", 13, 10
byte    "jmp wsprintfA", 13, 10
byte    13, 10
byte    "code_start:", 13, 10
prologue_nBytes         equ     $ - prologue

epilogue:
byte    "code_end:", 13, 10
byte    "    end startup", 13, 10
epilogue_nBytes         equ     $ - epilogue

dbDirective             byte    4 dup( ' ' ), "byte       "
dbDirective_nBytes      equ     $ - dbDirective

numberFormat            byte    " 0%02Xh", 0
numberFormat_nBytes     equ     $ - numberFormat

comma                   byte    ","
windowsNewline          byte    13, 10

write:
    push    0           ; space for nBytesWritten
    mov     ecx, esp    ; &nBytesWritten

    push    0           ; lpOverlapped
    push    ecx         ; &nBytesWritten
    push    ebx         ; nBytes
    push    eax         ; &s[0]
    push    globals.outputStreamHandle
    call    myWriteFile

    pop     eax         ; nBytesWritten
    ret

displayMachineCode:
    dmc_LocalsStruct    struct  dword_aligned
        numberStringLen     dword   ?
        numberString        byte    16*4 DUP( ? )
        fileHandle          dword   ?
        nBytesWritten       dword   ?
        byteIndex           dword   ?
    dmc_LocalsStruct    ends
    dmc_locals          textequ     <[ebp - sizeof dmc_LocalsStruct].dmc_LocalsStruct>

    mov     ebp, esp
    sub     esp, sizeof dmc_LocalsStruct

    ; Output prologue that makes MASM happy (placing machine code data in context):
    ; lea     eax, prologue
        mov     eax, globals.codeStart
        add     eax, prologue - code_start
    mov     ebx, prologue_nBytes
    call    write

    ; Output the machine code bytes.
    mov     dmc_locals.byteIndex, 0

dmc_lineLoop:
    ; loop start
            ; Output a db directive
        ;lea     eax, dbDirective
            mov     eax, globals.codeStart
            add     eax, dbDirective - code_start
        mov     ebx, dbDirective_nBytes
        call    write

    dmc_byteIndexingLoop:
        ; loop start
                ; Create string representation of a number
            mov     ecx, dmc_locals.byteIndex
            mov     eax, 0
            ;mov     al, byte ptr [code_start + ecx]
                mov     ebx, globals.codeStart
                mov     al, [ebx + ecx]
            push    eax
            ;push    offset numberFormat
                mov     eax, globals.codeStart
                add     eax, numberFormat - code_start
                push    eax
            lea     eax, dmc_locals.numberString
            push    eax
            call    mywsprintfA
            add     esp, 3*(sizeof dword)
            mov     dmc_locals.numberStringLen, eax

                ; Output string representation of number
            lea     eax, dmc_locals.numberString
            mov     ebx, dmc_locals.numberStringLen
            call    write

                ; Are we finished looping yet?
            inc     dmc_locals.byteIndex
            mov     ecx, dmc_locals.byteIndex
            cmp     ecx, code_end - code_start
            je      dmc_finalNewline
            and     ecx, 07h
            jz      dmc_after_byteIndexingLoop

                ; Output a comma
            ; lea     eax, comma
                mov     eax, globals.codeStart
                add     eax, comma - code_start
            mov     ebx, 1
            call    write
            jmp dmc_byteIndexingLoop
        ; loop end

    dmc_after_byteIndexingLoop:
            ; New line
        ; lea     eax, windowsNewline
            mov     eax, globals.codeStart
            add     eax, windowsNewline - code_start
        mov     ebx, 2
        call    write
        jmp     dmc_lineLoop;
    ; loop end

dmc_finalNewline:
        ; New line
    ; lea     eax, windowsNewline
        mov     eax, globals.codeStart
        add     eax, windowsNewline - code_start
    mov     ebx, 2
    call    write

    ; Output epilogue that makes MASM happy:
    ; lea     eax, epilogue
        mov     eax, globals.codeStart
        add     eax, epilogue - code_start
    mov     ebx, epilogue_nBytes
    call    write

    mov     esp, ebp
    ret

main:
    sub esp, sizeof GlobalsStruct
    mov edi, esp

    call    main_knownAddress
main_knownAddress:
    pop     eax
    sub     eax, main_knownAddress - code_start
    mov     globals.codeStart, eax

    push    STD_OUTPUT_HANDLE
    call    myGetStdHandle
    mov     globals.outputStreamHandle, eax

    call displayMachineCode

    ; Well behaved process exit:
    push 0                          ; Process exit code, 0 indicates success.
    call myExitProcess

code_end:
    end startup
Run Code Online (Sandbox Code Playgroud)

这是自我复制的输出:

.model flat, stdcall
option casemap :none

    extern  ExitProcess@4: proc
    extern  GetStdHandle@4: proc
    extern  WriteFile@20: proc
    extern  wsprintfA: proc

    .code
startup:
    jmp     code_start

jmp ExitProcess@4
jmp GetStdHandle@4
jmp WriteFile@20
jmp wsprintfA

code_start:
    byte        0E9h, 03Bh, 002h, 000h, 000h, 02Eh, 06Dh, 06Fh
    byte        064h, 065h, 06Ch, 020h, 066h, 06Ch, 061h, 074h
    byte        02Ch, 020h, 073h, 074h, 064h, 063h, 061h, 06Ch
    byte        06Ch, 00Dh, 00Ah, 06Fh, 070h, 074h, 069h, 06Fh
    byte        06Eh, 020h, 063h, 061h, 073h, 065h, 06Dh, 061h
    byte        070h, 020h, 03Ah, 06Eh, 06Fh, 06Eh, 065h, 00Dh
    byte        00Ah, 00Dh, 00Ah, 020h, 020h, 020h, 020h, 065h
    byte        078h, 074h, 065h, 072h, 06Eh, 020h, 020h, 045h
    byte        078h, 069h, 074h, 050h, 072h, 06Fh, 063h, 065h
    byte        073h, 073h, 040h, 034h, 03Ah, 020h, 070h, 072h
    byte        06Fh, 063h, 00Dh, 00Ah, 020h, 020h, 020h, 020h
    byte        065h, 078h, 074h, 065h, 072h, 06Eh, 020h, 020h
    byte        047h, 065h, 074h, 053h, 074h, 064h, 048h, 061h
    byte        06Eh, 064h, 06Ch, 065h, 040h, 034h, 03Ah, 020h
    byte        070h, 072h, 06Fh, 063h, 00Dh, 00Ah, 020h, 020h
    byte        020h, 020h, 065h, 078h, 074h, 065h, 072h, 06Eh
    byte        020h, 020h, 057h, 072h, 069h, 074h, 065h, 046h
    byte        069h, 06Ch, 065h, 040h, 032h, 030h, 03Ah, 020h
    byte        070h, 072h, 06Fh, 063h, 00Dh, 00Ah, 020h, 020h
    byte        020h, 020h, 065h, 078h, 074h, 065h, 072h, 06Eh
    byte        020h, 020h, 077h, 073h, 070h, 072h, 069h, 06Eh
    byte        074h, 066h, 041h, 03Ah, 020h, 070h, 072h, 06Fh
    byte        063h, 00Dh, 00Ah, 00Dh, 00Ah, 020h, 020h, 020h
    byte        020h, 02Eh, 063h, 06Fh, 064h, 065h, 00Dh, 00Ah
    byte        073h, 074h, 061h, 072h, 074h, 075h, 070h, 03Ah
    byte        00Dh, 00Ah, 020h, 020h, 020h, 020h, 06Ah, 06Dh
    byte        070h, 020h, 020h, 020h, 020h, 020h, 063h, 06Fh
    byte        064h, 065h, 05Fh, 073h, 074h, 061h, 072h, 074h
    byte        00Dh, 00Ah, 00Dh, 00Ah, 06Ah, 06Dh, 070h, 020h
    byte        045h, 078h, 069h, 074h, 050h, 072h, 06Fh, 063h
    byte        065h, 073h, 073h, 040h, 034h, 00Dh, 00Ah, 06Ah
    byte        06Dh, 070h, 020h, 047h, 065h, 074h, 053h, 074h
    byte        064h, 048h, 061h, 06Eh, 064h, 06Ch, 065h, 040h
    byte        034h, 00Dh, 00Ah, 06Ah, 06Dh, 070h, 020h, 057h
    byte        072h, 069h, 074h, 065h, 046h, 069h, 06Ch, 065h
    byte        040h, 032h, 030h, 00Dh, 00Ah, 06Ah, 06Dh, 070h
    byte        020h, 077h, 073h, 070h, 072h, 069h, 06Eh, 074h
    byte        066h, 041h, 00Dh, 00Ah, 00Dh, 00Ah, 063h, 06Fh
    byte        064h, 065h, 05Fh, 073h, 074h, 061h, 072h, 074h
    byte        03Ah, 00Dh, 00Ah, 063h, 06Fh, 064h, 065h, 05Fh
    byte        065h, 06Eh, 064h, 03Ah, 00Dh, 00Ah, 020h, 020h
    byte        020h, 020h, 065h, 06Eh, 064h, 020h, 073h, 074h
    byte        061h, 072h, 074h, 075h, 070h, 00Dh, 00Ah, 020h
    byte        020h, 020h, 020h, 062h, 079h, 074h, 065h, 020h
    byte        020h, 020h, 020h, 020h, 020h, 020h, 020h, 030h
    byte        025h, 030h, 032h, 058h, 068h, 000h, 02Ch, 00Dh
    byte        00Ah, 06Ah, 000h, 08Bh, 0CCh, 06Ah, 000h, 051h
    byte        053h, 050h, 0FFh, 077h, 004h, 0E8h, 074h, 0FEh
    byte        0FFh, 0FFh, 058h, 0C3h, 08Bh, 0ECh, 083h, 0ECh
    byte        050h, 08Bh, 007h, 005h, 005h, 000h, 000h, 000h
    byte        0BBh, 036h, 001h, 000h, 000h, 0E8h, 0D7h, 0FFh
    byte        0FFh, 0FFh, 0C7h, 045h, 0FCh, 000h, 000h, 000h
    byte        000h, 08Bh, 007h, 005h, 057h, 001h, 000h, 000h
    byte        0BBh, 00Fh, 000h, 000h, 000h, 0E8h, 0BFh, 0FFh
    byte        0FFh, 0FFh, 08Bh, 04Dh, 0FCh, 0B8h, 000h, 000h
    byte        000h, 000h, 08Bh, 01Fh, 08Ah, 004h, 019h, 050h
    byte        08Bh, 007h, 005h, 066h, 001h, 000h, 000h, 050h
    byte        08Dh, 045h, 0B4h, 050h, 0E8h, 02Ah, 0FEh, 0FFh
    byte        0FFh, 083h, 0C4h, 00Ch, 089h, 045h, 0B0h, 08Dh
    byte        045h, 0B4h, 08Bh, 05Dh, 0B0h, 0E8h, 08Fh, 0FFh
    byte        0FFh, 0FFh, 0FFh, 045h, 0FCh, 08Bh, 04Dh, 0FCh
    byte        081h, 0F9h, 068h, 002h, 000h, 000h, 074h, 02Bh
    byte        083h, 0E1h, 007h, 074h, 013h, 08Bh, 007h, 005h
    byte        06Eh, 001h, 000h, 000h, 0BBh, 001h, 000h, 000h
    byte        000h, 0E8h, 06Bh, 0FFh, 0FFh, 0FFh, 0EBh, 0AAh
    byte        08Bh, 007h, 005h, 06Fh, 001h, 000h, 000h, 0BBh
    byte        002h, 000h, 000h, 000h, 0E8h, 058h, 0FFh, 0FFh
    byte        0FFh, 0EBh, 086h, 08Bh, 007h, 005h, 06Fh, 001h
    byte        000h, 000h, 0BBh, 002h, 000h, 000h, 000h, 0E8h
    byte        045h, 0FFh, 0FFh, 0FFh, 08Bh, 007h, 005h, 03Bh
    byte        001h, 000h, 000h, 0BBh, 01Ch, 000h, 000h, 000h
    byte        0E8h, 034h, 0FFh, 0FFh, 0FFh, 08Bh, 0E5h, 0C3h
    byte        083h, 0ECh, 008h, 08Bh, 0FCh, 0E8h, 000h, 000h
    byte        000h, 000h, 058h, 02Dh, 04Ah, 002h, 000h, 000h
    byte        089h, 007h, 06Ah, 0F5h, 0E8h, 098h, 0FDh, 0FFh
    byte        0FFh, 089h, 047h, 004h, 0E8h, 023h, 0FFh, 0FFh
    byte        0FFh, 06Ah, 000h, 0E8h, 084h, 0FDh, 0FFh, 0FFh
code_end:
    end startup
Run Code Online (Sandbox Code Playgroud)