flat assembler
Message board for the users of flat assembler.

Index > Linux > Hexplore Assembly Demo 1

Author
Thread Post new topic Reply to topic
chastitywhiterose



Joined: 13 Oct 2025
Posts: 52
chastitywhiterose 05 Feb 2026, 12:19
I don't know why I did this to myself, but I rewrote my Hexplore program in pure Intel Assembly language for Linux.

I made a short video to show exactly what it looks like using it

https://www.youtube.com/watch?v=JtXdWqpmK4A

The source is twice as big as chastehex and is split between 4 source files. This program is a full interactive hex editor that runs in a terminal. Using only Linux system calls and ANSI escape sequences, I can display all the text where I want in the terminal and even do fancy things like highlight the currently selected byte with green.

Every time you switch to the previous/next page with the "Page Up" or "Page_Down" keys, it changes the file offset of a file named RAM and reads the new data. But it also saves the current page back to the file before switching pages or ending the program.

The entire executable produced from my source and FASM is less than 3 kilobytes and runs extremely fast. Obviously, this assembly source is not portable to other platforms like the C version is, but it was a fun challenge!

Code:
format ELF executable
entry main
include 'chastelib32.asm'
include "chasteio32.asm"
include 'hexplore-ansi.asm'

main:

mov [radix],16         ;can choose radix for integer output!
mov [int_width],1
mov [int_newline],0

mov     eax, 2         ; invoke SYS_FORK (kernel opcode 2)
int     80h
cmp     eax, 0         ; if eax is zero we are in the child process
jz      child          ; jump if eax is zero to child label

parent: ;the program that continues running after the child process ends

;before we begin the game loop, we will attempt to open a file named "RAM"

mov eax,RAM_filename ; this filename is defined later in this source
call putstring
call putline

call open

cmp eax,0
js main_end ;end program if RAM could not be opened

mov [RAM_filedesc],eax ; save the file descriptor number for later use

;before the main loop, we will load up to 256 bytes from the file
;I already wrote a function to do this in hexplore-ansi.asm
;and it handles it even when less than 256 bytes are read
call file_load_page

;this is the game loop where were get input and process it accordingly
loop_read_keyboard:    ;this loop keeps reading from the keyboard

mov eax,ansi_clear ;move to top left of screen
call putstring

mov eax,ansi_home ;move to top left of screen
call putstring

mov [red],0xFF
mov [green],0xFF
mov [blue],0xFF
call set_text_rgb

mov [x],16
mov [y],0
call move_cursor

mov eax,title
call putstring

mov [x],57
mov [y],0
call move_cursor

mov al,'X'
call putchar
mov al,'='
call putchar
mov eax,[RAM_x_select]
call putint
call putspace

mov al,'Y'
call putchar
mov al,'='
call putchar
mov eax,[RAM_y_select]
call putint

mov [x],0
mov [y],2
call move_cursor

call RAM_hexdump

;this section provides a visual way of knowing which byte is selected

mov eax,[RAM_y_select] ;which row is it on? Y vertical coordinate
mov [y],eax
add [y],2

;mov eax,[RAM_y_begin]
;add eax,18

mov eax,[RAM_x_select] ;which row is it on? Y vertical coordinate
mov ebx,3 ;we will multiply by 3 on the next line
mul ebx
add eax,8
mov [x],eax

;change color for brackets
mov [red],0xFF
mov [green],0x00
mov [blue],0xFF
call set_text_rgb

call move_cursor
mov al,'['
call putchar
add [x],3
call move_cursor
mov al,']'
call putchar
;end of brackets section

sub [x],2 ;go back a few characters
call move_cursor

;change color again for byte highlight
mov [red],0x00
mov [green],0xFF
mov [blue],0x00
call set_text_rgb

;obtain selected byte for proper indexing changes
mov ebx,[RAM_y_select]
shl ebx,4
add ebx,[RAM_x_select]
add ebx,RAM

mov eax,0
mov al,[ebx] ;get the byte at this address for printing
mov [int_width],2
call putint

;change color back to white
mov [red],0xFF
mov [green],0xFF
mov [blue],0xFF
call set_text_rgb

;where to move cursor in next function call
mov [x],0
mov [y],19
call move_cursor ;move the cursor before displaying help information

mov eax,help
call putstring

;where to move cursor in next function call
mov [x],0
mov [y],0
call move_cursor ;move the cursor before displaying keypress information

mov eax,0              ;zero eax to receive the key value in al
mov al,[key];          ;move the key pressed last time into al
call putint            ;print the number of this key
call putspace          ;print a space to keep it readable
call putchar           ;print the character in al register
call putline           ;print a line to make it easier to read

;will pause until a key is pressed
call getchar           ;call my function that reads a single byte from the keyboard

cmp al,'q'             ;test for q key. q stands for quit in this context
jz main_shutdown            ;jump to end of program if q was pressed
call hexplore_input    ;call the function to process the input and operate the editor
jmp loop_read_keyboard ;continue the game loop

main_shutdown: ;not just end, but save data and close the file properly

;when the program ends, we must first save bytes we changed to the file!
call file_save_page

mov eax,[RAM_filedesc] ;file number to close
call close

main_end:
mov eax, 1  ; invoke SYS_EXIT (kernel opcode 1)
mov ebx, 0  ; return 0 status on exit - 'No Errors'
int 80h

title db 'Hexplore : Chastity White Rose',0
help  db 'Arrows=Select_Byte q=quit page_up/down=navigate_file',0xA
      db '0-f=Enter_Hexadecimal',0

; This is the end of the parent process or main program
; The child process below only uses the stty command before returning to the parent proces

child:

;This child process disables line buffer with stty

;execute a command from the child process
    mov     edx, environment    ; address of environment variables
    mov     ecx, arguments      ; address of the arguments to pass to the commandline
    mov     ebx, command        ; address of the file to execute
    mov     eax, 11             ; invoke SYS_EXECVE (kernel opcode 11)
    int     80h

;this is the end of the child process which became the stty command and then terminated naturally

byte_brackets db '[  ]',0 ;for displaying brackets around selected byte



;The execve call requires the path to a program, arguments passed to that program, and environment variables if relevant
;these strings are the execve call data
command         db      '/bin/stty', 0h     ; command to execute
arg1            db      'cbreak', 0h
arguments       dd      command
                dd      arg1                ; arguments to pass to commandline (in this case just one)
                dd      0h                  ; end the struct
environment     dd      0h                  ; arguments to pass as environment variables (inthis case none) end the struct



;key is defined as dword even though only a byte is used
;this way, it loads into eax without trouble
key db 0,0

prefix_k db "k=",0

getchar:
push ebx
push ecx
push edx
mov edx,1     ;number of bytes to read
mov ecx,key   ;address to store the bytes
mov ebx,0     ;read from stdin
mov eax,3     ;invoke SYS_READ (kernel opcode 3)
int 80h       ;call the kernel
xor eax,eax   ;set eax to 0
mov al,[key]  ;set lowest part of eax to key read
pop edx
pop ecx
pop ebx
ret

RAM_filename db "RAM",0
RAM_filedesc dd 0 ; file descriptor
bytes_read dd 0
RAM db 0x100 dup '?',0
RAM_address dd 0 ;this is actually the address of the file which is used as a RAM substitute
RAM_x_select dd 0
RAM_y_select dd 0
RAM_y_begin dd 2

RAM_hexdump:

mov ebx,RAM
mov ebp,[RAM_address]

mov edx,0 ;the Y value for this loop
RAM_dump_loop:
mov ecx,0 ;the X value for this loop
mov eax,ebp
mov [int_width],8
call putint
call putspace
mov [int_width],2
mov eax,0
dump_byte_row:
mov al,[ebx]

normal_print:
call putint
call putspace
normal_print_skip:

inc ebx
inc ecx
cmp ecx,0x10;
jnz dump_byte_row

;optionally, print chars after hex bytes
call RAM_hexdump_text_row

call putline
add ebp,0x10
inc edx
cmp edx,0x10
jnz RAM_dump_loop

ret



RAM_hexdump_text_row:
push eax
push ebx
push ecx
push edx
sub ebx,0x10
mov ecx,0
mov eax,0
next_char:
mov al,[ebx]

;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,leave as is and proceed to next index
jmp next_index

not_printable:
mov al,'.' ;otherwise replace with placeholder value

next_index:
call putchar
inc ebx
inc ecx
cmp ecx,0x10
jnz next_char

pop edx
pop ecx
pop ebx
pop eax

ret
    


Description: Handles moving of cursor and changing text color with escape sequences. Writing this was the hardest part!
Download
Filename: hexplore-ansi.asm
Filesize: 9.63 KB
Downloaded: 29 Time(s)

Description: Shows error messages if a file can't be opened.
Download
Filename: chasteio32.asm
Filesize: 2.08 KB
Downloaded: 22 Time(s)

Description: integer and string output routines
Download
Filename: chastelib32.asm
Filesize: 6.15 KB
Downloaded: 38 Time(s)

Post 05 Feb 2026, 12:19
View user's profile Send private message Send e-mail Reply with quote
revolution
When all else fails, read the source


Joined: 24 Aug 2004
Posts: 20874
Location: In your JS exploiting you and your system
revolution 05 Feb 2026, 12:33
I think it would be safer to use SYS_IOCTL with a termios structure to change the tty mode, rather than using /bin/stty.
Post 05 Feb 2026, 12:33
View user's profile Send private message Visit poster's website Reply with quote
chastitywhiterose



Joined: 13 Oct 2025
Posts: 52
chastitywhiterose 05 Feb 2026, 15:48
revolution wrote:
I think it would be safer to use SYS_IOCTL with a termios structure to change the tty mode, rather than using /bin/stty.


I would be interested in this if I knew how as long as it doesn't require any extra dependencies. I am interested in reducing the size of the program and yet the fork and exec with /bin/stty was the only thing I could figure out.
Post 05 Feb 2026, 15:48
View user's profile Send private message Send e-mail Reply with quote
revolution
When all else fails, read the source


Joined: 24 Aug 2004
Posts: 20874
Location: In your JS exploiting you and your system
revolution 05 Feb 2026, 15:54
SYS_IOCTL is a syscall. The only dependency is the kernel.

For specific numbers on x86:
Code:
        SYS32_ioctl                     = 54
        SYSx32_ioctl                    = 0x40000000 + 514
        SYS64_ioctl                     = 16    
The numbers are the same for int 0x80
Post 05 Feb 2026, 15:54
View user's profile Send private message Visit poster's website Reply with quote
chastitywhiterose



Joined: 13 Oct 2025
Posts: 52
chastitywhiterose 05 Feb 2026, 16:06
revolution wrote:
SYS_IOCTL is a syscall. The only dependency is the kernel.

For specific numbers on x86:
Code:
        SYS32_ioctl                     = 54
        SYSx32_ioctl                    = 0x40000000 + 514
        SYS64_ioctl                     = 16    
The numbers are the same for int 0x80


I am interested but don't have a clue how to know which registers to load those numbers with and what they mean. All of my assembly knowledge is scattered through different tutorials and outdated references. But if there is a way to do this syscall and understand it, I could probable shave 100 bytes off my executable. What system call reference are you using?
Post 05 Feb 2026, 16:06
View user's profile Send private message Send e-mail Reply with quote
revolution
When all else fails, read the source


Joined: 24 Aug 2004
Posts: 20874
Location: In your JS exploiting you and your system
revolution 05 Feb 2026, 16:20
If the goal is to make the code as small as possible then using ioctl probably isn't the way. The termios structure needs some massaging to make sure all the options are set as required.
Post 05 Feb 2026, 16:20
View user's profile Send private message Visit poster's website Reply with quote
revolution
When all else fails, read the source


Joined: 24 Aug 2004
Posts: 20874
Location: In your JS exploiting you and your system
revolution 05 Feb 2026, 16:36
The basic layout would be like this:
Code:
struc termios2 {
        .input_flags            rd 1
        .output_flags           rd 1
        .control_flags          rd 1
        .local_flags            rd 1
        .line_discipline        rb 1
        .control_characters     rb 19   ; NCCS
        .input_speed            rd 1
        .output_speed           rd 1
}virtual at 0
        termios2 termios2
        sizeof.termios2 = $
end virtual

HANDLE_STD_OUTPUT       = 1
_IOC_WRITE              = 1
_IOC_READ               = 2
TCGETS2                 = _IOC_READ  shl 30 + sizeof.termios2 shl 16 + 0x542a
TCSETSW2                = _IOC_WRITE shl 30 + sizeof.termios2 shl 16 + 0x542c

termios termios2        ; instantiate in RAM

        ;                  eax        ebx                ecx      edx
        int 0x80        ; SYS_IOCTL, HANDLE_STD_OUTPUT, TCGETS2, termios

        ; make modifications to termios structure here

        int 0x80        ; SYS_IOCTL, HANDLE_STD_OUTPUT, TCSETSW2, termios    


Last edited by revolution on 05 Feb 2026, 16:53; edited 1 time in total
Post 05 Feb 2026, 16:36
View user's profile Send private message Visit poster's website Reply with quote
revolution
When all else fails, read the source


Joined: 24 Aug 2004
Posts: 20874
Location: In your JS exploiting you and your system
revolution 05 Feb 2026, 16:40
BTW: It it good manners to save the existing state in termios buffer, make modifications in a new buffer, and then restore the previous state at exit. Then the user doesn't get a shock with new console settings making them angry.
Post 05 Feb 2026, 16:40
View user's profile Send private message Visit poster's website Reply with quote
chastitywhiterose



Joined: 13 Oct 2025
Posts: 52
chastitywhiterose 05 Feb 2026, 17:25
revolution wrote:
The basic layout would be like this:
Code:
struc termios2 {
        .input_flags            rd 1
        .output_flags           rd 1
        .control_flags          rd 1
        .local_flags            rd 1
        .line_discipline        rb 1
        .control_characters     rb 19   ; NCCS
        .input_speed            rd 1
        .output_speed           rd 1
}virtual at 0
        termios2 termios2
        sizeof.termios2 = $
end virtual

HANDLE_STD_OUTPUT       = 1
_IOC_WRITE              = 1
_IOC_READ               = 2
TCGETS2                 = _IOC_READ  shl 30 + sizeof.termios2 shl 16 + 0x542a
TCSETSW2                = _IOC_WRITE shl 30 + sizeof.termios2 shl 16 + 0x542c

termios termios2        ; instantiate in RAM

        ;                  eax        ebx                ecx      edx
        int 0x80        ; SYS_IOCTL, HANDLE_STD_OUTPUT, TCGETS2, termios

        ; make modifications to termios structure here

        int 0x80        ; SYS_IOCTL, HANDLE_STD_OUTPUT, TCSETSW2, termios    


Yeah it probably won’t help me save size but I might make a separate example just to see if I can get this working at all. I have never actually used structures in fasm but this code looks legit.
Post 05 Feb 2026, 17:25
View user's profile Send private message Send e-mail Reply with quote
revolution
When all else fails, read the source


Joined: 24 Aug 2004
Posts: 20874
Location: In your JS exploiting you and your system
revolution 05 Feb 2026, 17:55
There are a bunch of flags that can be set. The list looks something like this:
Code:
; /usr/include/asm-generic/ioctls.h
_IOC_WRITE      = 1
_IOC_READ       = 2
TCGETS          = 0x5401
TCSETSW         = 0x5403        ; Allow the output buffer to drain, and set the current serial port settings
TIOCEXCL        = 0x540c
TIOCOUTQ        = 0x5411
FIONREAD        = 0x541b
TCGETS2         = _IOC_READ  shl 30 + sizeof.termios2 shl 16 + 0x542a
TCSETSW2        = _IOC_WRITE shl 30 + sizeof.termios2 shl 16 + 0x542c

; /usr/include/asm-generic/termbits.h
CBAUD           = 0010017o
 B0             = 0000000o              ; hang up
 B50            = 0000001o
 B75            = 0000002o
 B110           = 0000003o
 B134           = 0000004o
 B150           = 0000005o
 B200           = 0000006o
 B300           = 0000007o
 B600           = 0000010o
 B1200          = 0000011o
 B1800          = 0000012o
 B2400          = 0000013o
 B4800          = 0000014o
 B9600          = 0000015o
 B19200         = 0000016o
 B38400         = 0000017o
CSIZE           = 0000060o
  CS5           = 0000000o
  CS6           = 0000020o
  CS7           = 0000040o
  CS8           = 0000060o
CSTOPB          = 0000100o
CREAD           = 0000200o
PARENB          = 0000400o
PARODD          = 0001000o
HUPCL           = 0002000o
CLOCAL          = 0004000o
CBAUDEX         = 0010000o
   BOTHER       = 0010000o
   B57600       = 0010001o
  B115200       = 0010002o
  B230400       = 0010003o
  B460800       = 0010004o
  B500000       = 0010005o
  B576000       = 0010006o
  B921600       = 0010007o
 B1000000       = 0010010o
 B1152000       = 0010011o
 B1500000       = 0010012o
 B2000000       = 0010013o
 B2500000       = 0010014o
 B3000000       = 0010015o
 B3500000       = 0010016o
 B4000000       = 0010017o
CIBAUD          = 002003600000o ; input baud rate
CMSPAR          = 010000000000o ; mark or space (stick) parity
CRTSCTS         = 020000000000o ; flow control

; /usr/include/asm-generic/termbits.h

IGNBRK          = 0000001o
BRKINT          = 0000002o
IGNPAR          = 0000004o
PARMRK          = 0000010o
INPCK           = 0000020o
ISTRIP          = 0000040o
INLCR           = 0000100o
IGNCR           = 0000200o
ICRNL           = 0000400o
IUCLC           = 0001000o
IXON            = 0002000o
IXANY           = 0004000o
IXOFF           = 0010000o
IMAXBEL         = 0020000o
IUTF8           = 0040000o

ISIG            = 0000001o
ICANON          = 0000002o
XCASE           = 0000004o
ECHO            = 0000010o
ECHOE           = 0000020o
ECHOK           = 0000040o
ECHONL          = 0000100o
NOFLSH          = 0000200o
TOSTOP          = 0000400o
ECHOCTL         = 0001000o
ECHOPRT         = 0002000o
ECHOKE          = 0004000o
FLUSHO          = 0010000o
PENDIN          = 0040000o
IEXTEN          = 0100000o
EXTPROC         = 0200000o

OPOST           = 0000001o
OLCUC           = 0000002o
ONLCR           = 0000004o
OCRNL           = 0000010o
ONOCR           = 0000020o
ONLRET          = 0000040o
OFILL           = 0000100o
OFDEL           = 0000200o
NLDLY           = 0000400o
  NL0           = 0000000o
  NL1           = 0000400o
CRDLY           = 0003000o
  CR0_          = 0000000o
  CR1_          = 0001000o
  CR2_          = 0002000o
  CR3_          = 0003000o
TABDLY          = 0014000o
  TAB0          = 0000000o
  TAB1          = 0004000o
  TAB2          = 0010000o
  TAB3          = 0014000o
  XTABS         = 0014000o
BSDLY           = 0020000o
  BS0           = 0000000o
  BS1           = 0020000o
VTDLY           = 0040000o
  VT0           = 0000000o
  VT1           = 0040000o
FFDLY           = 0100000o
  FF0           = 0000000o
  FF1           = 0100000o    
For simple cases only a few of the flags will be needed.
Post 05 Feb 2026, 17:55
View user's profile Send private message Visit poster's website Reply with quote
Display posts from previous:
Post new topic Reply to topic

Jump to:  


< Last Thread | Next Thread >
Forum Rules:
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum
You cannot attach files in this forum
You can download files in this forum


Copyright © 1999-2026, Tomasz Grysztar. Also on GitHub, YouTube.

Website powered by rwasa.