3

To self-teach myself reverse engineering, I am writing small C programs and reversing them in order to understands how the compiler sees my code. However, I am having quite tough time understanding the stack concept w.r.t command line arguments.

So for a basic c code like this:

int main(int argc, char** argv){

    if(argc < 2){
        printf("1 argument needed!");   
    } else {
        printf("\n -- %s Entered", argv[1]);
        printf("\n%s -- is the first argument\n", argv[0]);
    }
    return 0;
}

The conversion is as follows:

IDA Pro Free DSM Code

The commented parts are where I am having a tough time:

var_10= qword ptr -10h
var_4= dword ptr -4
push    rbp ; understandable due to convention
mov     rbp, rsp ; same as above
sub     rsp, 10h ; why the space allocation? 
mov     [rbp+var_4], edi ; ?
mov     [rbp+var_10], rsi ; ?
6

First of all, you have to understand that there is a specification for all these things. These specifications differ from one assembly language to another and from one operating system to the other.

These global specification are called Application Binary Interface (ABI) and define, among other things, what we call the 'calling conventions' of functions. IDAPro seems to have found that your program is following the cdecl convention but I doubt it is correct, I think that the calling convention used here is fastcall within the amd64 SystemV ABI (see the wikipedia page about calling conventions). My guess is that you are using Linux...

So, the manipulation of rpb and rsp are just here to save the previous state of the stack-frame in order to restore it when you leave the function (you stack the content of rbp on the stack with the hope that you will be able to restore it when you leave the function at the end). But, you already understood this.

The space allocation (sub rsp, 10h) come from the fact that you have to store one int (argc) of 4 bytes and one pointer to a char* (argv) of 8 bytes. I know, when added it is only 12 bytes and not 16 bytes (10h). But, the access in memory for the CPU have been optimized when they are aligned (meaning that they start at an address which is a power of 2, or, if you consider hexadecimal representation, the address must end with a 0). So, the compiler decided to round-up the memory needed to align the data and be more efficient fetching it.

Then, you have the memory space available on the stack, now lets go fetch the data. So, for that you have to know what does the caller function before starting the function we are currently looking at.

In fact, the previous function, before calling the function we are in, has stored the arguments of our function in some registers. Here, it is important to agree that all the function (caller and callee) will use the same set of registers to pass the arguments from the caller to the callee.

So, usually, the first integer argument is stored in rdi and the second in rsi (the full list of registers is listed here. Here, as the first argument is argc an int, a 32-bit register is enough (4 bytes), so we use edi in place of rdi. And, as the second argument is a pointer (argv), we need the full 64-bit register rsi (8 bytes).

What do we do with this? Well, we store preciously argc and argv within our stack-frame in the memory we just allocated before.

Note that the address stored in rbp will not change all along the life of the current stack-frame (as we need it at the end to perform a leave and restore the address of the rbp of the previous stack-frame). So, most of the compilers will use rbp as point of reference to call the variables which are local to the current stack-frame. Thus, rbp+var_4 refer to argc and rbp+var_10 refer to argv from now.

Well, that is about all you really need to know about the fastcall convention in Linux. Now, the program should be more understandble to you.

Hope this helped!

  • awesome explanation! – Thomas Jul 22 '18 at 15:57
  • Is fastcall used in *nix world? – Biswapriyo Jul 22 '18 at 19:08
  • 1
    I use the terminology fastcall in a wider way that it should be. I meant: a calling convention that uses registers better than the stack to pass arguments. I said fastcall because it was one of the first to use regsiters and one of the most widely used. But, the registers used to pass arguments are changing between the Microsoft ABI and the SysemV ABI, so I guess that Unix is not using fastcall, even if the basic principles of fastcall (passing through registers) are used. – perror Jul 22 '18 at 19:20

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.