Tag: computers

  • Writing to video RAM

    One of the reasons DOS is the best platform for learning Intel Assembly language in my opinion is that it doesn’t prevent you from writing directly to video memory. People are unaware how much operating systems like Windows, and, to a lesser extent, Linux place limits on what you are allowed to do. The idea is that most people are not smart enough to be trusted with arbitrarily writing to video RAM, devices, etc.

    But that is where DOSBox comes in. Since it runs DOS inside an emulator, there is no danger. The program I am going to show you today is long and complicated but it does something that I can’t even do on Linux, write directly to video memory in multiple colors.

    Here is the source code that made all of that possible! I tested it to assemble with either FASM or NASM. If you assemble it and run it in DOSBox, or even a real DOS system, you will get something that looks like the picture above.

    org 100h
    
    main:
    
    ;set up the extra segment at the beginning of the program
    ;to point to the video memory segment in DOS text mode
    mov ax, 0xB800
    mov es, ax ; Or mov ds, ax
    
    
    ;80 columes times 25 rows is 2000 chars
    ;but since each character is two bytes
    ;4000 is the number of bytes to erase
    ;whatever character we write in this loop will fill the whole screen!
    
    mov bx,0
    screen_clear:
    mov [es:bx],word 0x0403
    add bx,2
    cmp bx,4000
    jnz screen_clear
    
    mov ax,title  ;the string we intend to write to video RAM
    mov ch,0x0F   ;the character attribute
    mov dx,0x0218
    call putstring_vram
    
    mov ax,v_str  ;the string we intend to write to video RAM
    mov ch,0x70   ;the character attribute
    mov dx,0x0401
    call putstring_vram
    
    ;set the starting attribute for characters and location
    mov ch,0x01   ;the character attribute
    mov dx,0x0501 ;x,y position of where text should start on screen
    
    loop_vram:
    cmp ch,0x10
    jz loop_vram_end
    mov ax,v_str  ;the string we intend to write to video RAM
    call putstring_vram
    add dx,0x100
    inc ch
    jmp loop_vram
    loop_vram_end:
    
    mov ax,4C00h
    int 21h
    
    title db 'Chastity Video RAM Demonstration!',0
    v_str db 'Hello World! This string will be written to video RAM using Assembly language!',0
    
    ;Unlike previous functions I wrote that use DOS interrupts to write text to the screen
    ;this one makes use of several registers which are not meant to be preserved
    ;registers ax,cx,and dx must be set before calling this function
    
    ;ax = address of string to write
    ;bx = copied from ax and used to index the string
    ;cx = used for character attribute(ch) and value(cl)
    ;dx = column(x pos) and row(y pos) of where string should be printed
    
    ;For this routine, I chose to copy the dx register to memory locations for clarity
    ;Yes, it wastes some bytes but at least I can read it as I am familiar with x,y coordinates
    ;Most importantly, the dx register is never modified in this function
    ;This is important because the main program may need to modify it in a loop
    ;For writing data in consecutive rows (e.g. integer sequences)
    
    x db 0
    y db 0
    
    putstring_vram:
    
    mov bx,ax             ;copy ax to bx for use as index register
    
    ;get x and y positions from each byte of dx register
    mov [x],dl
    mov [y],dh
    
    mov ax,80  ;set ax to 80 because there are 80 chars per row in text mode
    mul byte [y]    ;multiply with the y value
    mov byte [y],0  ;zero the y byte so we can add a 16 bit x value to ax
    add ax, word [x]
    
    shl ax,1 ;shift left once to account for two bytes per character
    
    mov di,ax ;we will use di as our starting output location
    
    putstring_vram_strlen_start:    ;this loop finds the length of the string as part of the putstring function
    
    cmp [bx],byte 0                 ;compare this byte with 0
    jz putstring_vram_strlen_end    ;if comparison was zero, jump to loop end because we have found the length/end of string
    mov cl,[bx]                     ;mov this character to cl
    mov [es:di],cx                  ;mov character and attribute set in ch(before calling this function) to extra_segment+di
    add di,2                        ;each character contains two bytes (ASCII+Attribute). We must add two here.
    inc bx                          ;increment bx to point to next character
    jmp putstring_vram_strlen_start ;jump to the start of the loop and keep trying until we find a zero
    
    putstring_vram_strlen_end:
    
    ret
    
  • 64 bit Linux chastehex

    I used my previous 32 bit Linux program and translated it to use the “syscall” instruction and the new registers and functions numbers for 64 bit Linux programs.

    The behavior of the program is identical to the 32 bit version of chastehex, however this was a stepping stone into 64 bit development in Assembly for Linux. There could be some use for 64 bit programs, but none that I can think of right now. Still, it is good to be prepared. Also, there was quite a bit of research that went into learning how to do the system calls in 64 bit mode. Those who are interested in learning how to make console programs for Linux in 64 bit can ask me questions about my source.

    The reason chastehex makes such a good program to base my standard library design on is that it does all the basic things that are needed for most programs.

    • Writes strings and numbers to standard output
    • opens and closes a file and reads or writes to it
    • accepts command line arguments and changes behavior accordingly
    • displays a message of how to use it if it is launched with no arguments
    • uses only functions supplied by the Linux kernel

    This post contains the full source for the 64 bit version of chastehex. You can also see the FASM forum thread about it here:

    https://board.flatassembler.net/topic.php?p=246358

    main.asm

    ;Linux 64-bit Assembly Source for chastehex
    ;a special tool originally written in C
    format ELF64 executable
    entry main
    
    include 'chastelib64.asm'
    include "chasteio64.asm"
    
    main:
    
    ;radix will be 16 because this whole program is about hexadecimal
    mov [radix],16 ; can choose radix for integer input/output!
    mov [int_newline],0 ;disable automatic printing of newlines after putint
    ;we will be manually printing spaces or newlines depending on context
    
    pop rax
    mov [argc],rax ;save the argument count for later
    
    ;first arg is the name of the program. we skip past it
    pop rax
    dec [argc]
    
    ;before we try to get the first argument as a filename, we must check if it exists
    cmp [argc],0
    jnz arg_open_file
    
    help:
    mov rax,help_message
    call putstring
    jmp main_end
    
    arg_open_file:
    
    pop rax
    dec [argc]
    mov [filename],rax ; save the name of the file we will open to read
    call putstring
    call putline
    
    call open
    
    cmp rax,0
    js main_end
    
    mov [filedesc],rax ; save the file descriptor number for later use
    mov [file_offset],0 ;assume the offset is 0,beginning of file
    
    ;check next arg
    cmp [argc],0 ;if there are no more args after filename, just hexdump it
    jnz next_arg_address ;but if there are more, jump to the next argument to process it as address
    
    hexdump:
    
    mov rdx,0x10         ;number of bytes to read
    mov rsi,byte_array   ;address to store the bytes
    mov rdi,[filedesc]   ;move the opened file descriptor into rdi
    mov rax,0            ;invoke SYS_READ (kernel opcode 0 on 64 bit Intel)
    syscall              ;call the kernel
    
    mov [bytes_read],rax
    
    ; call putint
    
    cmp rax,0
    jnz file_success ;if more than zero bytes read, proceed to display
    
    ;if the offset is zero, display EOF to indicate empty file
    ;otherwise, end without displaying this because there should already be bytes printed to the display
    cmp [file_offset],0
    jnz main_end
    
    call show_eof
    
    jmp main_end
    
    ; this point is reached if file was read from successfully
    
    file_success:
    ;mov rax,[filename]
    ;call putstring
    ;mov rax,file_opened_string
    ;call putstring
    
    mov rax,byte_array
    ;call putstring
    
    call print_bytes_row
    
    cmp [bytes_read],1 
    jl main_end ;if less than one bytes read, there is an error
    jmp hexdump
    
    ;address argument section
    next_arg_address:
    
    ;if there is at least one more arg
    pop rax ;pop the argument into rax and process it as a hex number
    dec [argc]
    call strint
    
    mov rdx,0          ;whence argument (SEEK_SET)
    mov rsi,rax        ;move the file cursor to this address
    mov rdi,[filedesc] ;move the opened file descriptor into rdi
    mov rax,8          ;invoke SYS_LSEEK (kernel opcode 8 on 64 bit Intel)
    syscall            ;call the kernel
    
    mov [file_offset],rax ;move the new offset
    
    ;check the number of args still remaining
    cmp [argc],0
    jnz next_arg_write ; if there are still arguments, skip this read section and enter writing mode
    
    read_one_byte:
    mov rdx,1            ;number of bytes to read
    mov rsi,byte_array   ;address to store the bytes
    mov rdi,[filedesc]   ;move the opened file descriptor into rdi
    mov rax,0            ;invoke SYS_READ (kernel opcode 0 on 64 bit Intel)
    syscall              ;call the kernel
    
    
    ;rax will have the number of bytes read after system call
    cmp rax,1
    jz print_byte_read ;if exactly 1 byte was read, proceed to print info
    
    call show_eof
    
    jmp main_end ;go to end of program
    
    ;print the address and the byte at that address
    print_byte_read:
    call print_byte_info
    
    ;this section interprets the rest of the args as bytes to write
    next_arg_write:
    cmp [argc],0
    jz main_end
    
    pop rax
    dec [argc]
    call strint ;try to convert string to a hex number
    
    ;write that number as a byte value to the file
    
    mov [byte_array],al
    
    mov rdx,1          ;write 1 byte
    mov rsi,byte_array ;pointer/address of byte to write
    mov rdi,[filedesc] ;write to this file descriptor
    mov rax,1          ;invoke SYS_WRITE (kernel opcode 1 on 64 bit systems)
    syscall            ;system call to write the message
    
    call print_byte_info
    inc [file_offset]
    
    jmp next_arg_write
    
    main_end:
    
    ;this is the end of the program
    ;we close the open file and then use the exit call
    
    mov rax,[filedesc] ;file number to close
    call close
    
    mov rax, 0x3C ; invoke SYS_EXIT (kernel opcode 0x3C (60 decimal) on 64 bit systems)
    mov rdi,0   ; return 0 status on exit - 'No Errors'
    syscall
    
    
    ;this function prints a row of hex bytes
    ;each row is 16 bytes
    print_bytes_row:
    mov rax,[file_offset]
    mov [int_width],8
    call putint
    call putspace
    
    mov rbx,byte_array
    mov rcx,[bytes_read]
    add [file_offset],rcx
    next_byte:
    mov rax,0
    mov al,[rbx]
    mov [int_width],2
    call putint
    call putspace
    
    inc rbx
    dec rcx
    cmp rcx,0
    jnz next_byte
    
    mov rcx,[bytes_read]
    pad_spaces:
    cmp rcx,0x10
    jz pad_spaces_end
    mov rax,space_three
    call putstring
    inc rcx
    jmp pad_spaces
    pad_spaces_end:
    
    ;optionally, print chars after hex bytes
    call print_bytes_row_text
    call putline
    
    ret
    
    space_three db '   ',0
    
    print_bytes_row_text:
    mov rbx,byte_array
    mov rcx,[bytes_read]
    next_char:
    mov rax,0
    mov al,[rbx]
    
    ;if char is below '0' or above '9', it is outside the range of these and is not a digit
    cmp al,0x20
    jb not_printable
    cmp al,0x7E
    ja not_printable
    
    printable:
    ;if char is in printable range,copy as is and proceed to next index
    jmp next_index
    
    not_printable:
    mov al,'.' ;otherwise replace with placeholder value
    
    next_index:
    mov [rbx],al
    inc rbx
    dec rcx
    cmp rcx,0
    jnz next_char
    mov [rbx],byte 0 ;make sure string is zero terminated
    
    mov rax,byte_array
    call putstring
    
    ret
    
    
    ;function to display EOF with address
    show_eof:
    
    mov rax,[file_offset]
    mov [int_width],8
    call putint
    call putspace
    mov rax,end_of_file_string
    call putstring
    call putline
    
    ret
    
    ;print the address and the byte at that address
    print_byte_info:
    mov rax,[file_offset]
    mov [int_width],8
    call putint
    call putspace
    mov rax,0
    mov al,[byte_array]
    mov [int_width],2
    call putint
    call putline
    
    ret
    
    end_of_file_string db 'EOF',0
    
    help_message db 'Welcome to chastehex! The tool for reading and writing bytes of a file!',0Ah,0Ah
    db 'To hexdump an entire file:',0Ah,0Ah,9,'chastehex file',0Ah,0Ah
    db 'To read a single byte at an address:',0Ah,0Ah,9,'chastehex file address',0Ah,0Ah
    db 'To write a single byte at an address:',0Ah,0Ah,9,'chastehex file address value',0Ah,0Ah,0
    
    ;variables for managing arguments
    argc dq 0
    filename dq 0 ; name of the file to be opened
    filedesc dq 0 ; file descriptor
    bytes_read dq 0
    file_offset dq 0
    
    
    
    
    ;where we will store data from the file
    byte_array db 17 dup ?
    

    chastelib64.asm

    ; This file is where I keep my function definitions.
    ; These are usually my string and integer output routines.
    
    ; function to print zero terminated string pointed to by register rax
    
    stdout dq 1 ; variable for standard output so that it can theoretically be redirected
    
    putstring:
    
    push rax
    push rbx
    push rcx
    push rdx
    
    mov rbx,rax ; copy rax to rbx as well. Now both registers have the address of the main_string
    
    putstring_strlen_start: ; this loop finds the lenge of the string as part of the putstring function
    
    cmp [rbx],byte 0 ; compare byte at address rdx with 0
    jz putstring_strlen_end ; if comparison was zero, jump to loop end because we have found the length
    inc rbx
    jmp putstring_strlen_start
    
    putstring_strlen_end:
    sub rbx,rax ;rbx will now have correct number of bytes
    
    ;write string using Linux Write system call
    ;https://www.chromium.org/chromium-os/developer-library/reference/linux-constants/syscalls/#x86_64-64-bit
    
    
    mov rdx,rbx      ;number of bytes to write
    mov rsi,rax      ;pointer/address of string to write
    mov rdi,[stdout] ;write to the STDOUT file
    mov rax,1        ;invoke SYS_WRITE (kernel opcode 1 on 64 bit systems)
    syscall          ;system call to write the message
    
    
    pop rdx
    pop rcx
    pop rbx
    pop rax
    
    ret ; this is the end of the putstring function return to calling location
    
    ;this is the location in memory where digits are written to by the putint function
    int_string     db 64 dup '?' ;enough bytes to hold maximum size 64-bit binary integer
    ; this is the end of the integer string optional line feed and terminating zero
    ; clever use of this label can change the ending to be a different character when needed 
    int_newline db 0Ah,0
    
    radix dq 2 ;radix or base for integer output. 2=binary, 8=octal, 10=decimal, 16=hexadecimal
    int_width dq 8
    
    ;this function creates a string of the integer in rax
    ;it uses the above radix variable to determine base from 2 to 36
    ;it then loads rax with the address of the string
    ;this means that it can be used with the putstring function
    
    intstr:
    
    mov rbx,int_newline-1 ;find address of lowest digit(just before the newline 0Ah)
    mov rcx,1
    
    digits_start:
    
    mov rdx,0;
    div qword [radix]
    cmp rdx,10
    jb decimal_digit
    jge hexadecimal_digit
    
    decimal_digit: ;we go here if it is only a digit 0 to 9
    add rdx,'0'
    jmp save_digit
    
    hexadecimal_digit:
    sub rdx,10
    add rdx,'A'
    
    save_digit:
    
    mov [rbx],dl
    cmp rax,0
    jz intstr_end
    dec rbx
    inc rcx
    jmp digits_start
    
    intstr_end:
    
    prefix_zeros:
    cmp rcx,[int_width]
    jnb end_zeros
    dec rbx
    mov [rbx],byte '0'
    inc rcx
    jmp prefix_zeros
    end_zeros:
    
    mov rax,rbx ; now that the digits have been written to the string, display it!
    
    ret
    
    
    ; function to print string form of whatever integer is in rax
    ; The radix determines which number base the string form takes.
    ; Anything from 2 to 36 is a valid radix
    ; in practice though, only bases 2,8,10,and 16 will make sense to other programmers
    ; this function does not process anything by itself but calls the combination of my other
    ; functions in the order I intended them to be used.
    
    putint: 
    
    push rax
    push rbx
    push rcx
    push rdx
    
    call intstr
    
    call putstring
    
    pop rdx
    pop rcx
    pop rbx
    pop rax
    
    ret
    
    ;this function converts a string pointed to by rax into an integer returned in rax instead
    ;it is a little complicated because it has to account for whether the character in
    ;a string is a decimal digit 0 to 9, or an alphabet character for bases higher than ten
    ;it also checks for both uppercase and lowercase letters for bases 11 to 36
    ;finally, it checks if that letter makes sense for the base.
    ;For example, G to Z cannot be used in hexadecimal, only A to F can
    ;The purpose of writing this function was to be able to accept user input as integers
    
    strint:
    
    mov rbx,rax ;copy string address from rax to esi because rax will be replaced soon!
    mov rax,0
    
    read_strint:
    mov rcx,0 ; zero rcx so only lower 8 bits are used
    mov cl,[rbx]
    inc rbx
    cmp cl,0 ; compare byte at address rdx with 0
    jz strint_end ; if comparison was zero, this is the end of string
    
    ;if char is below '0' or above '9', it is outside the range of these and is not a digit
    cmp cl,'0'
    jb not_digit
    cmp cl,'9'
    ja not_digit
    
    ;but if it is a digit, then correct and process the character
    is_digit:
    sub cl,'0'
    jmp process_char
    
    not_digit:
    ;it isn't a digit, but it could be perhaps and alphabet character
    ;which is a digit in a higher base
    
    ;if char is below 'A' or above 'Z', it is outside the range of these and is not capital letter
    cmp cl,'A'
    jb not_upper
    cmp cl,'Z'
    ja not_upper
    
    is_upper:
    sub cl,'A'
    add cl,10
    jmp process_char
    
    not_upper:
    
    ;if char is below 'a' or above 'z', it is outside the range of these and is not lowercase letter
    cmp cl,'a'
    jb not_lower
    cmp cl,'z'
    ja not_lower
    
    is_lower:
    sub cl,'a'
    add cl,10
    jmp process_char
    
    not_lower:
    
    ;if we have reached this point, result invalid and end function
    jmp strint_end
    
    process_char:
    
    cmp rcx,[radix] ;compare char with radix
    jae strint_end ;if this value is above or equal to radix, it is too high despite being a valid digit/alpha
    
    mov rdx,0 ;zero rdx because it is used in mul sometimes
    mul [radix]    ;mul rax with radix
    add rax,rcx
    
    jmp read_strint ;jump back and continue the loop if nothing has exited it
    
    strint_end:
    
    ret
    ;the next utility functions simply print a space or a newline
    ;these help me save code when printing lots of things for debugging
    
    space db ' ',0
    line db 0Dh,0Ah,0
    
    putspace:
    push rax
    mov rax,space
    call putstring
    pop rax
    ret
    
    putline:
    push rax
    mov rax,line
    call putstring
    pop rax
    ret
    

    chasteio64.asm

    ;this file is for managing the advanced Input and Output situations that occur when opening and closing files.
    ;I use the following references when using system calls.
    
    
    ;https://www.chromium.org/chromium-os/developer-library/reference/linux-constants/syscalls/#x86-32-bit
    ;https://www.chromium.org/chromium-os/developer-library/reference/linux-constants/errnos/
    
    
    ;before calling this function, make sure the rax register points to an address containing the filename as a zero terminated string
    ;this function opens a file for both reading and writing handle is returned in rax
    ;this function design is consistent with my other functions by using only rax as the input and output
    ;because it opens files for reading and writing, I do not need to be concerned with passing another argument for access mode
    
    ;However, this function actually does a whole lot more. It detects error codes by testing the sign bit and jumping to an error display system if rax is less than 0; Negative numbers are how errors are indicated on Linux. By turning the numbers positive, we get the actual error codes. The most common error codes that would occur are the following, either because a file doesn't exist, or because the user doesn't have permissions to read or write it.
    
    ; 2 0x02 ENOENT No such file or directory
    ;13 0x0d EACCES Permission denied
    
    open_error_message db 'File Error Code: ',0
    
    open:
    
    mov rsi,2   ;open file in read and write mode 
    mov rdi,rax ;filename should be in rax before this function was called
    mov rax,2   ;invoke SYS_OPEN (kernel opcode 2 on 64 bit systems)
    syscall     ;call the kernel
    
    cmp rax,0
    js open_error
    jmp open_end
    
    open_error:
    
    neg rax ;invert sign to get errno code
    push rax
    mov rax,open_error_message
    call putstring
    pop rax
    call putint
    call putline
    neg rax ;return rax to original sign
    
    open_end:
    
    ret
    
    ;this is the equivalent close call that expects rax to have the file handle we are closing
    ;technically it just passes it on to rdi but it is easier for me to remember if I use rax for everything
    
    close:
    
    mov rdi,rax ;file number to close
    mov rax,3   ;invoke SYS_CLOSE (kernel opcode 3 for 64 bit Intel)
    syscall     ;call the kernel
    
    ret
    
    
  • Chastity’s Source for ELF 64-bit executable creation

    I did it again. I decoded the 64 bit ELF header format for running an Assembly program on Intel CPUs running Linux. Although I don’t usually do 64 bit programming, I do have references which would allow me to translated my previous programs to use the 64-bit registers. Most of the time this is not needed for what they do.

    ;Chastity's Source for ELF 64-bit executable creation
    ;
    ;All data as defined in this file is based off of the specification of the ELF file format.
    ;I first looked at the type of file created by FASM's "format ELF executable" directive.
    ;It is great that FASM can create an executable file automatically. (Thanks Tomasz Grysztar, you are a true warrior!)
    ;However, I wanted to understand the format for theoretical use in other assemblers like NASM.
    
    ;The Github repository with the spec I used is here.
    ;<https://github.com/xinuos/gabi>
    ;And this is the wikipedia article which linked me to the specification document
    ;<https://en.wikipedia.org/wiki/Executable_and_Linkable_Format>
    
    ;This rest of this file contains a raw binary ELF64 header created using db,dw,dd,dq commands.
    ;After that, it proceeds to assemble a real "Hello World!" program
    
    ;Header for 64 bit ELF executable (with comments based on specification)
    
    db 0x7F,"ELF" ;ELFMAGIC: 4 bytes that identify this as an ELF file. The magic numbers you could say.
    db 2          ;EI_CLASS: 1=32-bit 2=64-bit
    db 1          ;EI_DATA: The endianness of the data. 1=ELFDATA2LSB 2=ELFDATA2MSB For Intel x86 this is always 1 as far as I know.
    db 1          ;EI_VERSION: 1=EV_CURRENT (ELF identity version 1) (which is current at time of specification Version 4.2 I was using)
    db 9 dup 0    ;padding zeros to bring us to address 0x10
    dw 2          ;e_type: 2=ET_EXEC (executable instead of object file)
    dw 0x3E       ;e_machine : 3=EM_386 (Intel 80386) 0x3E (AMD x86-64 architecture)
    dd 1          ;e_version: 1=EV_CURRENT (ELF object file version.)
    
    p_vaddr=0x400000
    e_entry=0x400078 ;we will be reusing this constant later 
    
    dq e_entry    ;e_entry: the virtual address at which the program starts
    dq 0x40       ;e_phoff: where in the file the program header offset is
    db 12 dup 0   ;e_shoff and e_flags are unused in this example,therefore all zeros
    dw 0x40       ;e_ehsize: size of the ELF header
    dw 0x38       ;e_phentsize: size of program header which happens after ELF header
    dw 1          ;e_phnum: How many program headers. Only 1 in this case
    dw 0x40       ;e_shentsize: Size of a section header
    dw 0          ;e_shnum number of section headers
    dw 0          ;e_shstrndx: section header string index (not used here)
    
    ;That is the end of the 0x40 byte (64 bytes decimal) ELF header. Sadly, this is not the end and a program header is also required (what drunk person made this format?)
    
    dd 1           ;p_type: 1=PT_LOAD
    dd 7           ;p_flags: permission flags: 7=4(Read)+2(Write)+1(Execute)
    dq 0           ;p_offset: Base address from file (zero)
    dq p_vaddr     ;p_vaddr: Virtual address in memory where the file will be.
    dq p_vaddr     ;p_paddr: Physical address. Same as previous
    
    image_size=0x1000 ;Chosen size for file and memory size. At minimum this must be as big as the actual binary file (code after header included)
                      ;By choosing a default size of 0x1000, I am assuming all assembly programs I write will be less than 4 kilobytes
    
    dq image_size  ;p_filesz: Size of file image of the segment. Must be equal to the file size or greater
    dq image_size  ;p_memsz: Size of memory image of the segment, which may be equal to or greater than file image.
    
    dq 0           ;p_align; Alignment (none)
    
    ;important FASM directives
    use64          ;tell assembler that 64 bit code is being used
    org e_entry    ;origin of new code begins at the entry point
    
    ;now, the actual hello world program
    mov rax,1   ; invoke SYS_WRITE (kernel opcode 1 on 64 bit systems)
    mov rdi,1   ; write to the STDOUT file
    mov rsi,msg ; pointer/address of string to write
    mov rdx,13  ; number of bytes to write
    syscall
    
    mov rax,0x3C ; invoke SYS_EXIT (kernel opcode 0x3C on 64 bit systems)
    mov rdi,0    ; return 0 status on exit - 'No Errors'
    syscall
    
    msg db 'Hello World!',0Ah
    
    ;This is the makefile I use when assembling and running this program
    
    ;main-fasm:
    ;	fasm ELF-64-hello.asm
    ;	chmod +x ELF-64-hello.bin
    ;	./ELF-64-hello.bin
    
  • Chastity’s Source for ELF 32-bit executable creation

    I wrote an example program using a custom made ELF-32 header using data declaration statements. I wrote many comments which reference the official specification. This was an exercise both in programming and also Technical Reading and Writing. I had to read enough of the specification PDF file to understand what I was doing. I then tried to write descriptive comments that at the very least I would understand when I need to remind myself how this format is created.

    ;Chastity's Source for ELF 32-bit executable creation
    ;
    ;All data as defined in this file is based off of the specification of the ELF file format.
    ;I first looked at the type of file created by FASM's "format ELF executable" directive.
    ;It is great that FASM can create an executable file automatically. (Thanks Tomasz Grysztar, you are a true warrior!)
    ;However, I wanted to understand the format for theoretical use in other assemblers like NASM.
    
    ;The Github repository with the spec I used is here.
    ;<https://github.com/xinuos/gabi>
    ;And this is the wikipedia article which linked me to the specification document
    ;<https://en.wikipedia.org/wiki/Executable_and_Linkable_Format>
    
    ;This file contains a raw binary ELF32 header created using db,dw,dd commands.
    ;After that, it proceeds to assemble a real "Hello World!" program
    
    ;Header for 32 bit ELF executable (with comments based on specification)
    
    db 0x7F,"ELF" ;ELFMAGIC: 4 bytes that identify this as an ELF file. The magic numbers you could say.
    db 1          ;EI_CLASS: 1=32-bit 2=64-bit
    db 1          ;EI_DATA: The endianness of the data. 1=ELFDATA2LSB 2=ELFDATA2MSB For Intel x86 this is always 1 as far as I know.
    db 1          ;EI_VERSION: 1=EV_CURRENT (ELF identity version 1) (which is current at time of specification Version 4.2 I was using)
    db 9 dup 0    ;padding zeros to bring us to address 0x10
    dw 2          ;e_type: 2=ET_EXEC (executable instead of object file)
    dw 3          ;e_machine : 3=EM_386 (Intel 80386)
    dd 1          ;e_version: 1=EV_CURRENT (ELF object file version.)
    
    p_vaddr=0x8048000
    e_entry=0x8048054 ;we will be reusing this constant later 
    
    dd e_entry    ;e_entry: the virtual address at which the program starts
    dd 0x34       ;e_phoff: where in the file the program header offset is
    db 8 dup 0    ;e_shoff and e_flags are unused in this example,therefore all zeros
    dw 0x34       ;e_ehsize: size of the ELF header
    dw 0x20       ;e_phentsize: size of program header which happens after ELF header
    dw 1          ;e_phnum: How many program headers. Only 1 in this case
    dw 0x28       ;e_shentsize: Size of a section header
    dw 0          ;e_shnum number of section headers
    dw 0          ;e_shstrndx: section header string index (not used here)
    
    ;That is the end of the 0x34 byte (52 bytes decimal) ELF header. Sadly, this is not the end and a program header is also required (what drunk person made this format?)
    
    dd 1          ;p_type: 1=PT_LOAD
    dd 0          ;p_offset: Base address from file (zero)
    dd p_vaddr    ;p_vaddr: Virtual address in memory where the file will be.
    dd p_vaddr    ;p_paddr: Physical address. Same as previous
    
    image_size=0x1000 ;Chosen size for file and memory size. At minimum this must be as big as the actual binary file (code after header included)
                      ;By choosing a default size of 0x1000, I am assuming all assembly programs I write will be less than 4 kilobytes
    
    dd image_size  ;p_filesz: Size of file image of the segment. Must be equal to the file size or greater
    dd image_size  ;p_memsz: Size of memory image of the segment, which may be equal to or greater than file image.
    
    dd 7           ;p_flags: permission flags: 7=4(Read)+2(Write)+1(Execute)
    dd 0           ;p_align; Alignment (none)
    
    ;important FASM directives
    use32          ;tell assembler that 32 bit code is being used
    org e_entry    ;origin of new code begins at the entry point
    
    ;now, the actual hello world program
    mov eax,4      ;invoke SYS_WRITE (kernel opcode 4 on 32 bit systems)
    mov ebx,1      ;write to the STDOUT file
    mov ecx,msg    ;pointer/address of string to write
    mov edx,13     ;number of bytes to write
    int 80h
    
    mov eax,1 ;function SYS_EXIT (kernel opcode 1 on 32 bit systems)
    mov ebx,0 ;return 0 status on exit - 'No Errors'
    int 80h   ;call Linux kernel with interrupt
    
    msg db 'Hello World!',0Ah
    
    ;This is the makefile I use when assembling and running this program
    
    ;main-fasm:
    ;	fasm ELF-32-hello.asm
    ;	chmod +x ELF-32-hello.bin
    ;	./ELF-32-hello.bin
    
    

    I made a repository for examples like this. Others may want to understand the header used on Linux systems.

    https://github.com/chastitywhiterose/ELF

  • Windows chastehex

    This is the source code of the Windows Assembly version of my program I invented called chastehex. I still need to record a video tutorial for how to use it, but it also displays a help message that is self-explanatory. However, most people are do not know how to assemble this from source. The main purpose of posting it on my blog is for my own backup. There is also an executable available on Github for those who want to try it out.

    https://github.com/chastitywhiterose/chastehex/tree/main/asm/windows

    It can arbitrarily read to or write from any address of a file with the correct arguments. It was designed to be part of a script to modify sections of files without requiring the installation of a hex editor. The best part is that it is free and open source because I wrote it and I say so. If you read the Github repository, you will also see that I released it under the GNU GENERAL PUBLIC LICENSE Version 3.

    main.asm

    format PE console
    include 'win32ax.inc'
    include 'chastelibw32.asm'
    
    main:
    
    mov [radix],16 ; Choose radix for integer output.
    mov [int_width],1
    
    ;get command line argument string
    call [GetCommandLineA]
    
    mov [arg_start],eax ;store start of arg string
    
    ;short routine to find the length of the string
    ;and whether arguments are present
    mov ebx,eax
    find_arg_length:
    cmp [ebx], byte 0
    jz found_arg_length
    inc ebx
    jmp find_arg_length
    found_arg_length:
    ;at this point, ebx has the address of last byte in string which contains a zero
    ;we will subtract to get and store the length of the string
    mov [arg_end],ebx
    sub ebx,eax
    mov eax,ebx
    mov [arg_length],eax
    
    ;display the arg string to make sure it is working correctly
    ;mov eax,[arg_start]
    ;call putstring
    ;call putline
    
    ;print the length in bytes of the arg string
    ;mov eax,[arg_length]
    ;call putint
    
    ;this loop will filter the string, replacing all spaces with zero
    mov ebx,[arg_start]
    arg_filter:
    cmp byte [ebx],' '
    ja notspace ; if char is above space, leave it alone
    mov byte [ebx],0 ;otherwise it counts as a space, change it to a zero
    notspace:
    inc ebx
    cmp ebx,[arg_end]
    jnz arg_filter
    
    arg_filter_end:
    
    ;optionally print first arg (name of program)
    ;mov eax,[arg_start]
    ;call putstring
    ;call putline
    
    ;get next arg (first one after name of program)
    call get_next_arg
    cmp eax,[arg_end]
    jz help
    
    mov [file_name],eax
    mov eax,file_open_message
    call putstring
    mov eax,[file_name]
    call putstring
    call putline
    
    jmp open_sesame
    
    help:
    
    mov eax,help_message
    call putstring
    
    jmp args_none
    
    open_sesame:
    
    ;open a file with the CreateFileA function
    ;https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea
    
    push 0           ;NULL: We are not using a template file
    push 0x80        ;FILE_ATTRIBUTE_NORMAL
    push 3           ;OPEN_EXISTING
    push 0           ;NULL: No security attributes
    push 0           ;NULL: Share mode irrelevant. Only this program reads the file.
    push 0x10000000  ;GENERIC_ALL access mode (Read+Write)
    push [file_name] ;
    call [CreateFileA]
    
    ;check eax for file handle or error code
    ;call putint
    cmp eax,-1
    jnz file_ok
    
    mov eax,file_error_message
    call putstring
    call [GetLastError]
    call putint
    jmp args_none ;end program if the file was not opened
    
    ;this label is jumped to when the file is opened correctly
    file_ok:
    
    mov [file_handle],eax
    
    mov [int_newline],0 ;disable automatic printing of newlines after putint
    ;we will be manually printing spaces or newlines depending on context
    
    ;before we proceed, we also check for more arguments.
    
    ;get next arg (first one after name of program)
    call get_next_arg
    cmp eax,[arg_end]
    jz hexdump ;proceed to normal hex dump if no more args
    
    ;otherwise interpret the arg as a hex address to seek to
    
    call strint
    mov [file_offset],eax
    mov eax,file_seek_message
    call putstring
    mov eax,[file_offset]
    call putint
    call putline
    
    ;seek to address of file with SetFilePointer function
    ;https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-setfilepointer
    push 0             ;seek from beginning of file (SEEK_SET)
    push 0             ;NULL: We are not using a 64 bit address
    push [file_offset] ;where we are seeking to
    push [file_handle] ;seek within this file
    call [SetFilePointer]
    
    ;check for more args
    call get_next_arg
    cmp eax,[arg_end]
    jz read_one_byte ;proceed to read one byte mode
    
    ;otherwise, write the rest of the arguments as bytes to the file!
    write_bytes:
    call strint
    mov [byte_array],al
    
    ;write only 1 byte using Win32 WriteFile system call.
    push 0              ;Optional Overlapped Structure 
    push 0              ;Optionally Store Number of Bytes Written
    push 1              ;Number of bytes to write
    push byte_array     ;address to store bytes
    push [file_handle]  ;handle of the open file
    call [WriteFile]
    
    mov eax,[file_offset]
    inc [file_offset]
    mov [int_width],8
    call putint
    call putspace
    
    mov eax,0
    mov al,[byte_array]
    mov [int_width],2
    call putint
    call putline
    
    ;check for more args
    call get_next_arg
    cmp eax,[arg_end]
    jnz write_bytes
    ;continue write if the args still exist
    ;otherwise end program
    jmp args_none
    
    read_one_byte:
    
    ;read only 1 byte using Win32 ReadFile system call.
    push 0              ;Optional Overlapped Structure 
    push bytes_read     ;Store Number of Bytes Read from this call
    push 1              ;Number of bytes to read
    push byte_array     ;address to store bytes
    push [file_handle]  ;handle of the open file
    call [ReadFile]
    
    cmp [bytes_read],1 
    jb print_EOF ;if less than one bytes read, there is an error
    
    mov eax,[file_offset]
    mov [int_width],8
    call putint
    call putspace
    
    mov eax,0
    mov al,[byte_array]
    mov [int_width],2
    call putint
    call putline
    
    jmp args_none
    
    hexdump:
    
    ;read bytes using Win32 ReadFile system call.
    push 0              ;Optional Overlapped Structure 
    push bytes_read     ;Store Number of Bytes Read from this call
    push 16             ;Number of bytes to read
    push byte_array     ;address to store bytes
    push [file_handle]  ;handle of the open file
    call [ReadFile]     ;all the data is in place, do the write thing!
    
    mov eax,[bytes_read]
    ;call putint
    ;mov eax,byte_array
    ;call putstring
    
    cmp [bytes_read],1 
    jb print_EOF ;if less than one bytes read, there is an error
    
    call print_bytes_row
    
    jmp hexdump
    
    print_EOF:
    
    mov eax,[file_offset]
    mov [int_width],8
    call putint
    call putspace
    
    mov eax,end_of_file
    call putstring
    call putline
    
    jmp args_none
    
    
    
    
    ;this loop is very safe because it only prints arguments if they are valid
    ;if the end of the args are reached by comparison of eax with [arg_end]
    ;then it will jump to args_none and proceed from there
    args_list:
    call get_next_arg
    cmp eax,[arg_end]
    jz args_none
    call putstring
    call putline
    jmp args_list
    args_none:
    
    ;Exit the process with code 0
    push 0
    call [ExitProcess]
    
    .end main
    
    arg_start  dd 0 ;start of arg string
    arg_end    dd 0 ;address of the end of the arg string
    arg_length dd 0 ;length of arg string
    arg_spaces dd 0 ;how many spaces exist in the arg command line
    
    ;variables for managing file IO.
    file_name dd 0
    bytes_read dd 0 ;how many bytes are read with ReadFile operation
    byte_array db 16 dup '?',0
    file_handle dd 0
    file_offset dd 0
    
    ;variables for displaying messages
    file_open_message db 'opening: ',0
    file_seek_message db 'seek: ',0
    file_error_message db 'error: ',0
    end_of_file db 'EOF',0
    read_error_message db 'Failure during reading of file. Error number: ',0
    
    help_message db 'Welcome to chastehex! The tool for reading and writing bytes of a file!',0Ah,0Ah
    db 'To hexdump an entire file:',0Ah,0Ah,9,'chastehex file',0Ah,0Ah
    db 'To read a single byte at an address:',0Ah,0Ah,9,'chastehex file address',0Ah,0Ah
    db 'To write a single byte at an address:',0Ah,0Ah,9,'chastehex file address value',0Ah,0Ah
    db 'The file must exist before you launch the program.',0Ah
    db 'This design was to prevent accidentally opening a mistyped filename.',0Ah,0
    
    ;function to move ahead to the next art
    ;only works after the filter has been applied to turn all spaces into zeroes
    get_next_arg:
    mov ebx,[arg_start]
    find_zero:
    cmp byte [ebx],0
    jz found_zero
    inc ebx
    jmp find_zero ; this char is not zero, go to the next char
    found_zero:
    
    find_non_zero:
    cmp ebx,[arg_end]
    jz arg_finish ;if ebx is already at end, nothing left to find
    cmp byte [ebx],0
    jnz arg_finish ;if this char is not zero we have found the next string!
    inc ebx
    jmp find_non_zero ;otherwise, keep looking
    
    arg_finish:
    mov [arg_start],ebx ; save this index to variable
    mov eax,ebx ;but also save it to ax register for use
    ret
    ;we can know that there are no more arguments when
    ;the either [arg_start] or eax are equal to [arg_end]
    
    
    
    ;this function prints a row of bytes
    ;each row is 16 bytes
    print_bytes_row:
    mov eax,[file_offset]
    mov [int_width],8
    call putint
    call putspace
    
    mov ebx,byte_array
    mov ecx,[bytes_read]
    add [file_offset],ecx
    next_byte:
    mov eax,0
    mov al,[ebx]
    mov [int_width],2
    call putint
    call putspace
    
    inc ebx
    dec ecx
    cmp ecx,0
    jnz next_byte
    
    call putline
    
    ret
    

    chastelibw32.asm

    ; This file is where I keep my function definitions.
    ; These are usually my string and integer output routines.
    
    ; function to print zero terminated string pointed to by register eax
    
    stdout dd 1 ; variable for standard output so that it can theoretically be redirected
    
    putstring:
    
    push eax
    push ebx
    push ecx
    push edx
    
    mov ebx,eax ; copy eax to ebx as well. Now both registers have the address of the main_string
    
    putstring_strlen_start: ; this loop finds the lenge of the string as part of the putstring function
    
    cmp [ebx],byte 0 ; compare byte at address ebx with 0
    jz putstring_strlen_end ; if comparison was zero, jump to loop end because we have found the length
    inc ebx
    jmp putstring_strlen_start
    
    putstring_strlen_end:
    sub ebx,eax ;ebx will now have correct number of bytes
    
    ;Write String using Win32 WriteFile system call.
    push 0              ;Optional Overlapped Structure 
    push 0              ;Optionally Store Number of Bytes Written
    push ebx            ;Number of bytes to write
    push eax            ;address of string to print
    push -11            ;STD_OUTPUT_HANDLE = Negative Eleven
    call [GetStdHandle] ;use the above handle
    push eax            ;eax is return value of previous function
    call [WriteFile]    ;all the data is in place, do the write thing!
    
    pop edx
    pop ecx
    pop ebx
    pop eax
    
    ret ; this is the end of the putstring function return to calling location
    
    ;this is the location in memory where digits are written to by the putint function
    int_string     db 32 dup '?' ;enough bytes to hold maximum size 32-bit binary integer
    ; this is the end of the integer string optional line feed and terminating zero
    ; clever use of this label can change the ending to be a different character when needed 
    int_newline db 0Ah,0
    
    radix dd 2 ;radix or base for integer output. 2=binary, 8=octal, 10=decimal, 16=hexadecimal
    int_width dd 8
    
    ;this function creates a string of the integer in eax
    ;it uses the above radix variable to determine base from 2 to 36
    ;it then loads eax with the address of the string
    ;this means that it can be used with the putstring function
    
    intstr:
    
    mov ebx,int_newline-1 ;find address of lowest digit(just before the newline 0Ah)
    mov ecx,1
    
    digits_start:
    
    mov edx,0;
    div dword [radix]
    cmp edx,10
    jb decimal_digit
    jge hexadecimal_digit
    
    decimal_digit: ;we go here if it is only a digit 0 to 9
    add edx,'0'
    jmp save_digit
    
    hexadecimal_digit:
    sub edx,10
    add edx,'A'
    
    save_digit:
    
    mov [ebx],dl
    cmp eax,0
    jz intstr_end
    dec ebx
    inc ecx
    jmp digits_start
    
    intstr_end:
    
    prefix_zeros:
    cmp ecx,[int_width]
    jnb end_zeros
    dec ebx
    mov [ebx],byte '0'
    inc ecx
    jmp prefix_zeros
    end_zeros:
    
    mov eax,ebx ; now that the digits have been written to the string, display it!
    
    ret
    
    
    ; function to print string form of whatever integer is in eax
    ; The radix determines which number base the string form takes.
    ; Anything from 2 to 36 is a valid radix
    ; in practice though, only bases 2,8,10,and 16 will make sense to other programmers
    ; this function does not process anything by itself but calls the combination of my other
    ; functions in the order I intended them to be used.
    
    putint: 
    
    push eax
    push ebx
    push ecx
    push edx
    
    call intstr
    
    call putstring
    
    pop edx
    pop ecx
    pop ebx
    pop eax
    
    ret
    
    ;this function converts a string pointed to by eax into an integer returned in eax instead
    ;it is a little complicated because it has to account for whether the character in
    ;a string is a decimal digit 0 to 9, or an alphabet character for bases higher than ten
    ;it also checks for both uppercase and lowercase letters for bases 11 to 36
    ;finally, it checks if that letter makes sense for the base.
    ;For example, G to Z cannot be used in hexadecimal, only A to F can
    ;The purpose of writing this function was to be able to accept user input as integers
    
    strint:
    
    mov ebx,eax ;copy string address from eax to ebx because eax will be replaced soon!
    mov eax,0
    
    read_strint:
    mov ecx,0 ; zero ecx so only lower 8 bits are used
    mov cl,[ebx]
    inc ebx
    cmp cl,0 ; compare byte at address edx with 0
    jz strint_end ; if comparison was zero, this is the end of string
    
    ;if char is below '0' or above '9', it is outside the range of these and is not a digit
    cmp cl,'0'
    jb not_digit
    cmp cl,'9'
    ja not_digit
    
    ;but if it is a digit, then correct and process the character
    is_digit:
    sub cl,'0'
    jmp process_char
    
    not_digit:
    ;it isn't a digit, but it could be perhaps and alphabet character
    ;which is a digit in a higher base
    
    ;if char is below 'A' or above 'Z', it is outside the range of these and is not capital letter
    cmp cl,'A'
    jb not_upper
    cmp cl,'Z'
    ja not_upper
    
    is_upper:
    sub cl,'A'
    add cl,10
    jmp process_char
    
    not_upper:
    
    ;if char is below 'a' or above 'z', it is outside the range of these and is not lowercase letter
    cmp cl,'a'
    jb not_lower
    cmp cl,'z'
    ja not_lower
    
    is_lower:
    sub cl,'a'
    add cl,10
    jmp process_char
    
    not_lower:
    
    ;if we have reached this point, result invalid and end function
    jmp strint_end
    
    process_char:
    
    cmp ecx,[radix] ;compare char with radix
    jae strint_end ;if this value is above or equal to radix, it is too high despite being a valid digit/alpha
    
    mov edx,0 ;zero edx because it is used in mul sometimes
    mul [radix]    ;mul eax with radix
    add eax,ecx
    
    jmp read_strint ;jump back and continue the loop if nothing has exited it
    
    strint_end:
    
    ret
    
    
    
    ;the next utility functions simply print a space or a newline
    ;these help me save code when printing lots of things for debugging
    
    space db ' ',0
    line db 0Dh,0Ah,0
    
    putspace:
    push eax
    mov eax,space
    call putstring
    pop eax
    ret
    
    putline:
    push eax
    mov eax,line
    call putstring
    pop eax
    ret