In the last post of learning shellcode, we are taking a look at how to create our own simple shellcode in 32-bit intel architecture but this time let's switch to ARM architecture so we have a better understanding of how the program works in more low level.
note: this post is heavily based on Azeria labs you guys can check the corresponding post in here. All I do is just reiterating the concept and probably explain a little bit more detail about it
Let's start from the beginning "Hello world"
in this section, we are going to just create a really simple hello world program from scratch using arm assembly. Type the following code below and save it as "hello_world.s"let's break it down in detail what is this code really doing:
1. ".data" section used to initialized data or constant in memory and remember this section cannot be changed during runtime by default.
In the source code, we declare two variable which is "string" that will hold value "hello world\n" notice before setting the value of the variable there is what is called "string definition directives" this used to allocate memory for the string, we use .asciz to automatically put null byte at the end of the string after declaration
next is variable "after_string" that holds the value of the length of the "string" variable
2 ".text" section used to contain all of the source code, unlike normal higher programming assembly used "_start" as the main function.
3. As you know in x86 architecture function call all the parameters are stored in the stack, like the following figure:
link:http://hack-r.com/cdecl-and-stdcall-calling-conventions-stack-clearing-and-the-eax-register/
but in ARM architecture they used the register to stored the value of the parameter for the function called. ARM used register R0-R6 to stored the parameter and R7 for the syscall value
in _start: we can see that in order to print "hello world" to the screen we use syscall from write() that has three parameters.
ssize_t write(int fd, const void *buf, size_t count);
1st is for storing file descriptor which is one for stdout
2nd is the actual string
3rd will be the size of the string
then we put the syscall value (which is 4) for write() to R7
finally, we execute by executing swi instruction
_exit used just for making a clean exit and addr_of_string used for referencing the memory of string "hello world" stored in .data section
Now, all we have to do is just compile and link the source code, like this:
Dissect the program:
COOL! we just making our first shellcode, let's try to load in the gdb and see how the process is really executetake a look at _start function we can see that the location of string variable is converted to pc, #16 you don't have to worry about this changing this is just how the compiler work and then take a look at the last instruction, as we recall we used swi instruction and once we compiled it change to "svc" again you don't have to worry.
this is basically because swi instruction is deprecated and the compiler smart enough to change it using svc
try to put a breakpoint at the beginning of the _start function, once you run it, take a look at the register notice that there is no value assigned to it at first
but as you step it through until svc instruction it fills with our parameter and at the end, it will spit out the "hello world" string again
Now let's create shellcode that can invoke a shell:
we are ready to create our first shellcode, type this source code into your favorite text editor:I think this is pretty straightforward, to invoke shell we need to call execve that have this parameter.
int execve(const char *filename, char *const argv[],char *const envp[]);
we only care about the first parameter and the rest of it could be set to 0 and for execve it has syscall 11
remember if we use a direct number such as (0-9) we need to put # before the number.
then you may be wondering what's with the pc, #12 assign to R0. you don't have to worry about it this is just another way to refer to the memory location of "/bin/sh\0"
compile it and we can see that we able to call a shell :)
unfortunately, if we dump the binary we are going to have a lot of null bytes that make our shellcode useless.
so how to get rid of null bytes then?
you can use thumb mode in the arm. In arm assembly there are two modes first is the arm mode which takes 32 bits or space and thumb mode that takes 16 bits.
.code 32 and .code 16 is just a directive to tell the compiler to generate a selected set of instruction, in here we define two-part first is the arm code (.code 32) and the thumb code (.code 16)
.code32 section we want to force the program to switch to the thumb mode adding 1 to the pc register and then take a jump using b/blx/bx command
once we enter thumb mode it will start executing the instruction at .code16 then in arm thumb mode we must align the code to be 4 bytes so we add nop with mov r5,r5 (this is basically don't do anything)
to zero out r1 and r2 we can use eor which is stands for Exclusive OR and then rest of it is self explanatory
but we are not done yet since as you can see at the last .word instruction we still have null bytes append to the string /bin/sh right ?
how do we overcome this ?
you can modify the value of the string at runtime to be append with a null byte, in the following way
we will be using instruction strb stands for store byte, as you know our R2 register contains a null byte so we can overwrite the "x" string which is located at the 7th character in the string stored in the register R0.
so at runtime, we change "/bin/shx" to "/bin/sh\0" after executing strb instruction
if you compile it, it will encounter segmentation fault as I mentioned earlier that .data cannot be edit at runtime by default so you must append extra parameter "-N" at ld utility to prevent this error
check the opcode, wallaaa we are free from null bytes. Now all we have to do is dump the binary and convert it into hexadecimal so we can use it in our exploit
Use this following python script:
Cool so in this post we just building a simple shellcode that could give us a shell and that's all folks I hope you enjoy this material
see you at the next post of arm buffer overflow
Comments
Post a Comment