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
$ 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
Post a Comment