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

Comments

One response to “Windows chastehex”

  1. judenaklebs Avatar

    Self explanatory? First you have to interpret your creepy number Greek to me before it explains anything to my word brain!

    Liked by 1 person

Leave a reply to judenaklebs Cancel reply