Learning How to Write in Assembly for Linux 2

Now that we know the basics of writing in assembly, lets do something neat with it. How about, WRITING OUT TO A FILE!? Okay, maybe not as exciting as it sounds, but we're starting with baby steps. Lets start out with our basic assembly file:

     $ vim file
     section .text
     global _start
     _start:

Our first question is how we deal with creating a new file. Do we want to hardcode a filename into the program or do we want to make it user-definable? Lets go ahead and make it dynamic, its more fun this way anyway.
When a program is called from the command line, all of the command line arguments are stored in the stack, with the last argument being pushed on to the stack first, and the final push to the stack is a value of how many items were pushed on to the stack, like this:

     0x3              ;top of stack
     file               ;name of program
     argument_1
     argument_2 ;remember, the count starts at '0'; there are 4 items in the stack
     ...                 ;stack continues

The first thing our program should do is to pop off the top of the stack and make sure that there are the proper number of arguments given with the command. We can use the cmp command for this check (cmp subtracts val1 with val2 and sets the register flags).

     $ vim file
     section .text
     global _start
     _start:
           pop eax
           cmp eax, 0x2


We can then check the zero flag to see if it is set, meaning the previous cmp equals (or does not equal) 0. If it does not equal zero, lets print an error message and exit. If it does equal zero, that means we have the proper number of arguments and we can continue with the file creation. Lets write this much out and add some place holders for some functions that we will need as well:

     $ vim file
     section .text
     global _start
     _start:
           pop eax
           cmp eax, 0x2
           je create
           mov eax, 4      ;sys_write()
           mov ebx, 1      ;to write out to console
           mov ecx, fail   ;msg to write
           mov edx, flen  ;length of fail msg
           int 0x80 
           jmp die


create:


write:


die:


section .data
fail db 'This program requires a single filename as an argument',0xa
flen equ $-fail


Alright, our assembly is starting to take shape. To create a file, we'll need to lookup the system call for creat() for whichever system architecture we are currently using. I'm using x86, so the system call is 0x8 or just 8 (since 8decimal == 8hex we dont need to explicitly define which base we are using). According to $ man 2 creat we could have also used open() with an additional 'mode' setting (i.e. read_only, or read_write, or append, etc), but we'll stick with creat(). It specifies that it requires the pathname as well as the permissions to be set (or mode). We can write the mode in octal (by specifying the digits with 'Q'), and we can easily pop the program name and then pop again to get the intended new file name. Once we do this, it is also a good idea to make sure the file descriptor that is returned in eax is a valid fd (i.e. don't assume you can create a new file named anything).

     $ vim file
     section .text
     global _start
     _start:
           pop eax
           cmp eax, 0x2
           je create
           mov eax, 4      ;sys_write()
           mov ebx, 1      ;to write out to console
           mov ecx, fail   ;msg to write
           mov edx, flen  ;length of fail msg
           int 0x80 
           jmp die



create:
          mov eax, 8
          pop ebx
          pop ebx
          mov ecx, 00644Q     ;this is for rw_r__r__ permissions
          int 0x80

          test eax, eax     ;this checks for valid file descriptors
          js die                ;if the sign is negative the fd is bad
          call write
         

die:

write:


section .data
fail db 'This program requires a single filename as an argument',0xa
flen equ $-fail

We could stop here if all you wanted to do was create a new file. But since that's not our goal let's push on. If you are observant you'll noticed that I switched the order of our die and write procedures. I did this because when we use the call function, our child procedure may return to this position using the ret call. This is much like in C using a return 0 on an int function. Once our write procedure is finished and we return to the call we can then fall right on through to the die function to exit cleanly. Since we have already done an exercise on how to write out to the console, I will go ahead and spill the beans: Writing out to a file is exactly the same only different. The difference is instead of putting a '1' in ebx, we put the file descriptor in there. Everything else is familiar.

     $ vim file
     section .text
     global _start
     _start:
           pop eax
           cmp eax, 0x2
           je create
           mov eax, 4      ;sys_write()
           mov ebx, 1      ;to write out to console
           mov ecx, fail   ;msg to write
           mov edx, flen  ;length of fail msg
           int 0x80 
           jmp die



create:
          mov eax, 8
          pop ebx
          pop ebx
          mov ecx, 00644Q     ;this is for rw_r__r__ permissions
          int 0x80

          test eax, eax     ;this checks for valid file descriptors
          js die                ;if the sign is negative the fd is bad
          call write
         

die:

write: 
          mov ebx, eax             ;the fd is still located in eax
          mov eax, 4                ;sys_write()
          mov ecx, message 
          mov edx, mlen
          int 0x80


section .data
fail db 'This program requires a single filename as an argument',0xa
flen equ $-fail
message db 'Hello world!'
mlen equ $-message

Once the writing is done to the file, we need to close the open file descriptor and then return and finish the die procedures. The system call to close the fd is, naturally, close() and is represented on x86 systems as '6'.



     $ vim file
     section .text
     global _start
     _start:
           pop eax
           cmp eax, 0x2
           je create
           mov eax, 4      ;sys_write()
           mov ebx, 1      ;to write out to console
           mov ecx, fail   ;msg to write
           mov edx, flen  ;length of fail msg
           int 0x80 
           jmp die



create:
          mov eax, 8
          pop ebx
          pop ebx
          mov ecx, 00644Q     ;this is for rw_r__r__ permissions
          int 0x80

          test eax, eax     ;this checks for valid file descriptors
          js die                ;if the sign is negative the fd is bad
          call write
         

die:
          mov ebx, eax         ;this will preserve any error codes if present
          mov eax, 1            ;sys_exit()
          int 0x80

write: 
          mov ebx, eax             ;the fd is still located in eax
          mov eax, 4                ;sys_write()

          mov ecx, message 
          mov edx, mlen
          int 0x80


         mov eax, 6         ;sys_close()
         int 0x80
         ret



section .data
fail db 'This program requires a single filename as an argument',0xa
flen equ $-fail
message db 'Hello world!'
mlen equ $-message

And there we go, now we can write out to a file in assembly. But what happens if we type in a name of a file that already exists? Here's a hint: don't try that on any file you care about. It will create your new file in its place. I think a good feature to have would be to add checking into it so that it wont override the file. In the meantime, have fun.

          geno

Comments