Tag: api

  • 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
    
  • Chastity Windows Reverse Engineering Notes

    The Windows version of FASM includes header files for the Windows API. It also includes some examples, but not a single one of them were a simple “Hello World” console program.

    Fortunately, I was able to find one that actually assembled and ran on the FASM forum. Below is the source code.

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

    To get it working required me to keep the include files in a location I remembered and set the include environment variable. I found this out from the FASM Windows documentation.

    set include=C:\fasm\INCLUDE

    However, there was another problem. The example that I found online and got working uses macros, most specifically, one called “invoke”. While this works if you include the headers, it hides the details of what is actually happening. Therefore, I decided to reverse engineer the process by using NOP instructions to sandwich the bytes of machine code.

    90 hex is the byte for NOP (No OPeration). So to extract the macro call that exits the program, I use this.

    db 10h dup 90h
    invoke  ExitProcess,0
    db 10h dup 90h
    

    Then I disassemble the executable and find the actual instructions given.

    ndisasm main.exe -b 32 > disasm.txt

    As simple as this method is, it actually works. For example, this output is given as part of the output.

    0000022D  90                nop
    0000022E  90                nop
    0000022F  90                nop
    00000230  90                nop
    00000231  90                nop
    00000232  90                nop
    00000233  90                nop
    00000234  90                nop
    00000235  90                nop
    00000236  90                nop
    00000237  90                nop
    00000238  90                nop
    00000239  90                nop
    0000023A  90                nop
    0000023B  90                nop
    0000023C  90                nop
    0000023D  6A00              push dword 0x0
    0000023F  FF1548204000      call dword near [0x402048]
    00000245  90                nop
    00000246  90                nop
    00000247  90                nop
    00000248  90                nop
    00000249  90                nop
    0000024A  90                nop
    0000024B  90                nop
    0000024C  90                nop
    0000024D  90                nop
    0000024E  90                nop
    0000024F  90                nop
    00000250  90                nop
    00000251  90                nop
    00000252  90                nop
    00000253  90                nop
    00000254  90                nop
    

    There can be no mistake that it is that location between the NOPs where the relevant code is. Therefore, I replaced the macro that exits the program with this.

    ;Exit the process with code 0
     push 0
     call [ExitProcess]
    

    What I learned

    As I repeated the same process for the other macros, I found that the way system calls in Windows work is that the numbers are pushed onto the stack in the reverse order they are needed. I was able to decode the macros and get a working program without the use of “invoke”. Here is the full source!

    format PE console
    include 'win32ax.inc'
    
    main:
    
    ;Write 13 bytes from a string to standard output
    push 0              ;this must be zero. I have no idea why!  
    push 13             ;number of bytes to write
    push main_string    ;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 [WriteConsole] ;all the data is in place, do the write thing!
    
    ;Exit the process with code 0
    push 0
    call [ExitProcess]
    
    .end main
    
    main_string db 'Hello World!',0Ah
    

    I don’t know much about the Windows API, but I did discover some helpful information when I searched the names of these functions that were part of the original macros.

    https://learn.microsoft.com/en-us/windows/console/getstdhandle
    https://learn.microsoft.com/en-us/windows/console/writeconsole

    Why I did this

    You might wonder why I even bothered to get a working Windows API program in Assembly Language. After all, I am a Linux user to the extreme. However, since Windows is the most used operating system for the average person, I figured that if I write any useful programs in Assembly for 32-bit Linux, I can probably port them over to Windows by changing just a few things.

    Since my toy programs are designed to write text to the console anyway and I don’t do GUI stuff unless I am programming a game in C with SDL, I now have enough information from this small Hello World example to theoretically write anything to the console that I might want to in an official Windows executable.

    Obviously I need to learn a lot more for bigger programs but this is the first Assembly program I have ever gotten working for Windows, despite my great success with DOS and Linux, which are easier because they are better documented and ARE TAUGHT BETTER by others. People programming Assembly in Windows have been ruined by macros which hide the actual instructions being used. As I learn how these things work, I will be sure to pass on the information to others!