Last modified 2017-09-25 00:25:11 CDT

telnet in x86 assembly

Simple telnet client written in 32-bit x86 assembly, using Linux system calls only. Uses select() to handle input from both stdin and the network socket. Host name resolution is not implemented, so it just takes an IP address and a port. Works great with the towel.blinkenlights.nl Star Wars ASCII animation.

Also available in my x86 assembly git repository here: https://github.com/vsergeev/x86asm/blob/master/telnet.asm

; Telnet written in 32-bit x86 assembly, using Linux system calls only
; Vanya A. Sergeev - vsergeev at gmail - 01/12/2009
;
; Assemble and link with
;  nasm -f elf telnet.asm -o telnet.o
;  ld -s telnet.o -o telnet
;
; Tested on i686 Linux, kernel version 2.6.27
;
; Usage: ./telnet <IP address> <port>
; Note: host name resolution is not implemented. This version only takes IP
; addresses.
;
 
; Star Wars Telnet Demo
; towel.blinkenlights.nl -> 94.142.241.111
; Run:
; $ ./telnet 94.142.241.111 23

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

section .data
    msgInvalidArguments:    db 'Invalid IP address or port supplied!',10,0
    msgInvalidArgumentsLen: equ $-msgInvalidArguments

    msgErrorSocket:     db 'Error creating socket!',10,0
    msgErrorSocketLen:  equ $-msgErrorSocket

    msgErrorConnect:    db 'Error connecting to server!',10,0
    msgErrorConnectLen: equ $-msgErrorConnect

    msgErrorSelect:     db 'Error with select()!',10,0
    msgErrorSelectLen:  equ $-msgErrorSelect

    msgUsage:       db 'Usage: ./telnet <IP address> <port>',10,0
    msgUsageLen:        equ $-msgUsage

    ; Arguments for socket(): socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
    socketArgs:     dd 2,1,6

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

section .bss
    ; Socket file descriptor returned by socket()
    sockfd:         resd    1

    ; Storage for the 4 IP octets 
    ipOctets        resb    4

    ; Storage for the connection port represented in one 16-bit word
    ipPort          resw    1

    ; Arguments for connect(): 
    ;   connect(sockfd, serverSockaddr, serversockaddrLen);
    connectArgs     resd    3

    ; The read file descriptor array for select()
    masterReadFdArray   resb    128
    checkReadFdArray    resb    128
    readFdArrayLen      equ 128

    ; sockaddr_in structure that needs to be filled in for the
    ; connect() system call.
    ;   struct sockaddr_in {
    ;       short            sin_family;
        ;       unsigned short   sin_port;
        ;       struct in_addr   sin_addr;
        ;       char             sin_zero[8];
    ;   };
    serverSockaddr      resb    (2+2+4+8)
    serverSockaddrLen   equ 16

    ; Read buffer for reading from stdin and the socket
    readBuffer      resb    1024
    readBufferLen       resd    1
    readBufferMaxLen    equ 1024

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

section .text
    global _start

_start:
    ; Pop argc
    pop eax
    
    ; Check if we have the correct number of arguments (2), for the 
    ; program name and IP address.
    cmp eax, 3  
    je parse_program_arguments
    
    ; Otherwise, print the usage and quit.
    push msgUsage
    push msgUsageLen
    call cWriteString
    add esp, 8
    call cExit

parse_program_arguments:
    ; Set the direction flag to increment, so edi/esi are INCREMENTED
    ; with their respective load/store instructions.
    cld

    ; Pop the program name string
    pop eax

    ;;; Convert the port and IP address strings to numbers ;;;

    ; Next on the stack is the IP address
    ; Convert the IP address string to four byte sized octets.
    call cStrIP_to_Octets
    add esp, 4
    ; Check for errors
    cmp eax, 0
    jl invalid_program_arguments

    ; Next on the stack is the port
    ; Convert the port string to a 16-bit word.
    call cStrtoul
    add esp, 4
    mov [ipPort], eax
    ; Check for errors
    cmp eax, 0
    jge network_open_socket

    ; Otherwise, print error for invalid arguments and quit.
    invalid_program_arguments:
    push msgInvalidArguments
    push msgInvalidArgumentsLen
    call cWriteString
    add esp, 8
    call cExit

network_open_socket:
    ;;; Open a socket and store it in sockfd ;;;

    ; Syscall socketcall(1, ...); for socket();
    mov eax, 102
    mov ebx, 1
    mov ecx, socketArgs
    int 0x80

    ; Copy our socket file descriptor to our variable sockfd
    mov [sockfd], eax

    ; Check if socket() returned a valid socket file descriptor
    cmp eax, 0
    jge network_connect

    ; Otherwise, print error creating socket and quit.
    push msgErrorSocket
    push msgErrorSocketLen
    call cWriteString
    add esp, 8
    call cExit
    
network_connect:
    ;;; Setup the argument to connect() and call connect() ;;;
    
    ; Fill in the sockaddr_in structure with the
    ; network family, port, and IP address information,
    ; along with the zeros in the zero field.
    mov edi, serverSockaddr

    ; Store the network family, AF_INET = 2
    mov al, 2
    stosb
    mov al, 0
    stosb

    ; Store the port, in network byte order (big endian).
    
    ; High byte first
    mov ax, [ipPort]
    ; Truncate the lower byte
    shr ax, 8
    stosb

    ; Low byte second
    mov ax, [ipPort]
    stosb

    ; Store the 4 octets of the IP address, reading from the
    ; ipOctets 4-byte array and copying to the respective
    ; locations in the serverSockaddr structure.
    mov esi, ipOctets
    ; movsb * 4 = movsd
    movsd

    ; Zero out the remaining 8 bytes of the structure
    mov al, 0
    mov ecx, 8
    rep stosb

    ; Setup the array that will hold the arguments for connect 
    ; we are passing through the socketcall() system call.
    mov edi, connectArgs
    ; sockfd
    mov eax, [sockfd]
    stosd
    ; Pointer to serverSockaddr structure
    mov eax, serverSockaddr
    stosd
    ; serverSockaddrlen
    mov eax, serverSockaddrLen
    stosd
    
    ; Syscall socketcall(3, ...); for connect();
    mov eax, 102
    mov ebx, 3
    mov ecx, connectArgs
    int 0x80
    
    ; Check if connect() returned a success
    cmp eax, 0
    jge network_setup_file_descriptors

    ; Otherwise, print error creating socket and quit.
    push msgErrorConnect
    push msgErrorConnectLen
    call cWriteString
    add esp, 8
    jmp network_premature_exit

network_setup_file_descriptors:
    ;;; Clear the read fd array, add stdin and the socket fd to the
    ;;; array. ;;;

    ; Point edi to the beginning of the read file descriptor array
    mov edi, masterReadFdArray
    
    ; Zero out all 128 bytes of the read file descriptor array
    mov al, 0
    mov ecx, readFdArrayLen
    rep stosb

    ; Add stdin, file descriptor 0, to the read file descriptor array
    mov edi, masterReadFdArray
    mov al, 1
    stosb

    ; Reset edi to the beginning of the read file descriptor array
    mov edi, masterReadFdArray
    ; Copy the value of the socket file descriptor to eax
    mov eax, [sockfd]
    
    ; Divide eax by 8, so we can find the offset from the beginning of
    ; the file descriptor array, so we can set the necessary bit for
    ; the socket file descriptor in the read file descriptor array.
    shr eax, 3
    ; Increment the pointer by the offset
    add edi, eax

    ; Make another copy of the socket file descriptor in ecx
    mov ecx, [sockfd]
    ; Isolate the bit offset 
    and cl, 0x7
    ; Left shift a 1 to make a bit mask at that bit offset
    mov al, 1
    shl al, cl
    ; Bitwise OR the bit high at correct bit position in the array
    or [edi], al

network_read_write_loop:
    ; Copy over the master read file descriptor array to the
    ; checking read file descriptor array, which we will pass
    ; to select and check which file descriptors are set/unset.
    mov edi, checkReadFdArray
    mov esi, masterReadFdArray
    mov ecx, readFdArrayLen
    rep movsb

    ; Syscall select(sockfd+1, readFdArray, 0, 0, 0);
    ; nfds, the first argument of select, is the highest
    ; file descriptor + 1, in our case it would be sockfd+1,
    ; since stdin is always file descriptor 0.
    mov eax, 142
    mov ebx, [sockfd]
    inc ebx
    mov ecx, checkReadFdArray
    mov edx, 0
    mov esi, 0
    mov edi, 0
    int 0x80

    ; Check the return value of select for errors
    cmp eax, 0

    jg check_read_file_descriptors
    ; Otherwise, print error calling select and quit
    push msgErrorSelect
    push msgErrorSelectLen
    call cWriteString
    add esp, 8
    jmp network_premature_exit
    
    check_read_file_descriptors:
    check_stdin_file_descriptor:
        ;;; Check if the stdin file descriptor is set ;;;

        ; Read the first byte (where the first bit, stdin, will be 
        ; located) of the updated file descriptor array
        mov esi, checkReadFdArray
        lodsb
        ; Mask the first bit in the array
        and al, 0x01
        ; Check if it is set
        cmp al, 0x01
        jne check_socket_file_descriptor
        ; Otherwise, it is set, and we need to read the data into a 
        ; buffer, and then write it to the socket
        call cReadStdin
        call cWriteSocket
    
    ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

    check_socket_file_descriptor:
        ;;; Check if the socket file descriptor is set ;;;

        ; Reset esi to the beginning of the read file descriptor array
        mov esi, checkReadFdArray
        ; Copy the value of the socket file descriptor to eax
        mov edx, 0
        mov eax, [sockfd]

        ; Divide eax by 8, so we can find the offset from the beginning
        ; of the file descriptor array, so we can set the necessary bit
        ; for the socket file descriptor in the read file descriptor 
        ; array.
        shr eax, 3
        ; Increment the pointer by the offset
        add esi, eax

        ; Make another copy of the socket file descriptor in ecx
        mov ecx, [sockfd]
        ; Isolate the bit offset
        and cl, 0x7
        ; Left shift a 1 to make a bit mask at that bit offset
        mov bl, 1
        shl bl, cl
    
        ; Read the byte and mask the correct bit for the socket fd
        lodsb
        and al, bl
        ; Check if it is set
        cmp al, bl
        jne check_socket_file_descriptor_done
        ; Otherwise, it is set, and we need to read the data into a 
        ; buffer, and then write it to stdout
        call cReadSocket
        call cWriteStdout

    ; Loop back to the select() system call to check for more data
    check_socket_file_descriptor_done:
    jmp network_read_write_loop

network_premature_exit:
network_close_socket:
    ; Syscall close(sockfd);
    mov eax, 6
    mov ebx, [sockfd]
    int 0x80

    call cExit

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;
; cExit
;   Exits program with the exit() syscall.
;       arguments: none
;       returns: nothing
;
cExit:
    ; Syscall exit(0);
    mov eax, 1
    mov ebx, 0
    int 0x80
    ret

;
; cReadStdin
;   Reads from stdin into readBuffer.
;   Sets readBuffLen with number of bytes read.
;       arguments: none
;       returns: number of bytes read on success, -1 on error, in eax
;
cReadStdin:
    ; Syscall read(0, readBuffer, readBufferMaxLen);
    mov eax, 3
    mov ebx, 0
    mov ecx, readBuffer
    mov edx, readBufferMaxLen
    int 0x80

    mov [readBufferLen], eax
    ret

;
; cReadSocket
;   Reads from the socket sockfd into readBuffer.
;   Sets readBuffLen with number of bytes read.
;       arguments: none
;       returns: number of bytes read on success, -1 on error, in eax
;
cReadSocket:
    ; Syscall read(sockfd, readBuffer, readBufferMaxLen);
    mov eax, 3
    mov ebx, [sockfd]
    mov ecx, readBuffer
    mov edx, readBufferMaxLen
    int 0x80    

    mov [readBufferLen], eax
    ret

;
; cWriteStdout:
;   Writes readBufferLen bytes of readBuff to stdout.
;       arguments: none
;       returns: number of bytes written on success, -1 on error, in eax
;
cWriteStdout:
    ; Syscall write(1, readBuffer, readBufferLen);
    mov eax, 4
    mov ebx, 1
    mov ecx, readBuffer
    mov edx, [readBufferLen]
    int 0x80
    ret

;
; cWriteSocket
;   Writes readBufferLen bytes of readBuff to the socket sockfd.
;       arguments: none
;       returns: number of bytes written on success, -1 on error, in eax
;
cWriteSocket:
    ; Syscall write(sockfd, readBuff, readBuffLen);
    mov eax, 4
    mov ebx, [sockfd]
    mov ecx, readBuffer
    mov edx, [readBufferLen]
    int 0x80
    ret

;
; cWriteString
;   Prints message loaded on stack to stdout.
;       arguments: message to write, message length
;       returns: nothing
;
cWriteString:
    push ebp
    mov ebp, esp

    ; Syscall write(stdout, message, message length);
    mov eax, 4
    mov ebx, 1
    ; Message poitner
    mov ecx, [ebp+12]
    ; Message length
    mov edx, [ebp+8]
    int 0x80

    mov esp, ebp
    pop ebp
    ret

;
; cStrIP_to_Octets
;   Parses an ASCII IP address string, e.g. "127.0.0.1", and stores the
;   numerical representation of the 4 octets in the ipOctets variable.
;       arguments: pointer to the IP address string
;       returns: 0 on success, -1 on failure
;
cStrIP_to_Octets:
    push ebp
    mov ebp, esp
    
    ; Allocate space for a temporary 3 digit substring variable of the IP
    ; address, used to parse the IP address.
    sub esp, 4
    
    ; Point esi to the beginning of the string
    mov esi, [ebp+8]

    ; Reset our counter, we'll use this to iterate through the
    ; 3 digits of each octet.
    mov ecx, 0

    ; Reset our octet counter, this is to keep track of the 4
    ; octets we need to fill.
    mov edx, 0

    ; Point edi to the beginning of the temporary
    ; IP octet substring
    mov edi, ebp
    sub edi, 4

    string_ip_parse_loop:
        ; Read the next character from the IP string
        lodsb
        ; Increment our counter
        inc ecx

        ; If we encounter a dot, process this octet
        cmp al, '.'
        je octet_complete
        ; If we encounter a null character, process this
        ; octet.
        cmp al, 0
        je null_byte_encountered
        ; If we're already on our third digit,
        ; process this octet.
        cmp ecx, 4
        jge octet_complete

        ; Otherwise, copy the character to our
        ; temporary octet string.
        stosb

        jmp string_ip_parse_loop
        
    null_byte_encountered:
        ; Check to see if we are on the last octet yet
        ; (current octet would be equal to 3)
        cmp edx, 3
        ; If so, everything is working normally
        je octet_complete
        ; Otherwise, this is a malformed IP address,
        ; and we will return -1 for failure
        mov eax, -1
        jmp malformed_ip_address_exit
    
    octet_complete:
        ; Null terminate our temporary octet variable.
        mov al, 0
        stosb

        ; Save our position in the IP address string
        push esi
        ; Save our octet counter
        push edx

        ; Send off our temporary octet string to our cStrtoul
        ; function to turn it into a number.
        mov eax, ebp
        sub eax, 4
        push eax
        call cStrtoul
        add esp, 4

        ; Check if we had any errors converting the string,
        ; if so, go straight to exit (eax will hold error through)
        cmp eax, 0
        jl malformed_ip_address_exit

        ; Restore our octet counter
        pop edx
    
        ; Copy the octet data to the current IP octet
        ; in our IP octet array.    
        mov edi, ipOctets
        add edi, edx
        ; cStrtoul saved the number in eax, so we should
        ; be fine writing al to [edi].
        stosb

        ; Increment our octet counter.
        inc edx
        
        ; Restore our position in the IP address string
        pop esi
        ; Reset the position on the temporary octet string
        mov edi, ebp
        sub edi, 4
        ; Continue to processing the next octet
        mov ecx, 0

        cmp edx, 4
        jl string_ip_parse_loop

    ; Return 0 for success
    mov eax, 0

    malformed_ip_address_exit:
    mov esp, ebp
    pop ebp
    ret

;
; cStrtoul
;   Converts a number represented in an ASCII string to an unsigned 32-bit
;   integer.
;       arguments: pointer to the string
;       returns: 32-bit integer stored in eax
;
cStrtoul:
    push ebp
    mov ebp, esp
    ; Allocate space for the multiply operand
    sub esp, 4

    ; Point esi to the beginning of the string
    mov esi, [ebp+8]

    ; Make a copy of the string address in edi
    mov edi, esi

    string_length_loop:
        ; Load the next byte from the string
        lodsb
        ; Compare the byte to the null byte
        cmp al, 0
        ; Continue to loop until the null byte is reached
        jne string_length_loop

    ; Copy the address of the null byte + 1 and subtract the
    ; address of the string to have the string length in ebx 
    mov ebx, esi
    sub ebx, edi
    ; Decrement by one to account for the null byte
    dec ebx

    ; Ensure that the string length > 0
    cmp ebx, 0
    jle premature_exit

    ; Use eax to hold the current character
    mov eax, 0
    ; Use ecx to hold the digit position in terms of powers of ten
    mov ecx, 0
    ; Use edx to hold the final result
    mov edx, 0
    ; Set esi back to the beginning of the string so we can traverse it
    mov esi, edi
    digits_count_loop:
        ; Read the next digit into al
        lodsb
        ; Decrement our string length counter
        dec ebx

        ; Start out at 10^0 = 1
        mov ecx, 1
        mov edi, 0
        ; Check if we need to multiply by any more powers of 10 
        cmp ebx, edi
        ; If not, then ecx = 10^0 = 1, so we can skip the exponent
        ; multiplication loop.
        je exponent_loop_skip

        ; Otherwise, multiply ecx by 10 for however many powers
        ; the current digit requires
        exponent_loop:
            imul ecx, 10
            inc edi
            cmp ebx, edi
            jg exponent_loop
        
        exponent_loop_skip:
            ; Check if the character is 0 or greater
            cmp al, 48
            jge lower_bound_met
            ; Otherwise, set the result to 0 and exit
            mov eax, -1
            jmp premature_exit
        
        lower_bound_met:
            ; Check if the character is 9 or less
            cmp al, 57
            jle upper_bound_met
            ; Otherwise, set the result to 0 and exit
            mov eax, -1
            jmp premature_exit
    
        upper_bound_met:    
    
        ; Subtract 48, the ASCII code for '0', from the character,
        ; leaving just the digit in al
        sub al, 48

        ; Multiply the powers of ten with the digit
        mov [ebp-4], eax
        imul ecx, [ebp-4]

        ; Add this digit value to the final result
        add edx, ecx

        ; Continue looping until we have gone through all the digits
        cmp ebx, 0
        jne digits_count_loop   
    
    ; Move the result to eax
    mov eax, edx

    premature_exit:
    mov esp, ebp
    pop ebp
    ret

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

Comments

Creative Commons License