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
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;