Calling Conventions
A more in-depth look into parameters for 32-bit and 64-bit programs
One Parameter
Source
Let's have a quick look at the source:
#include <stdio.h>
void vuln(int check) {
if(check == 0xdeadbeef) {
puts("Nice!");
} else {
puts("Not nice!");
}
}
int main() {
vuln(0xdeadbeef);
vuln(0xdeadc0de);
}Pretty simple.
If we run the 32-bit and 64-bit versions, we get the same output:
Just what we expected.
Analysing 32-bit
Let's open the binary up in radare2 and disassemble it.
If we look closely at the calls to sym.vuln, we see a pattern:
We literally push the parameter to the stack before calling the function. Let's break on sym.vuln.
The first value there is the return pointer that we talked about before - the second, however, is the parameter. This makes sense because the return pointer gets pushed during the call, so it should be at the top of the stack. Now let's disassemble sym.vuln.
Here I'm showing the full output of the command because a lot of it is relevant. radare2 does a great job of detecting local variables - as you can see at the top, there is one called arg_8h. Later this same one is compared to 0xdeadbeef:
Clearly that's our parameter.
So now we know, when there's one parameter, it gets pushed to the stack so that the stack looks like:
Analysing 64-bit
Let's disassemble main again here.
Hohoho, it's different. As we mentioned before, the parameter gets moved to rdi (in the disassembly here it's edi, but edi is just the lower 32 bits of rdi, and the parameter is only 32 bits long, so it says EDI instead). If we break on sym.vuln again we can check rdi with the command
Awesome.
Multiple Parameters
Source
32-bit
We've seen the full disassembly of an almost identical binary, so I'll only isolate the important parts.
It's just as simple - push them in reverse order of how they're passed in. The reverse order becomes helpful when you db sym.vuln and print out the stack.
So it becomes quite clear how more parameters are placed on the stack:
64-bit
So as well as rdi, we also push to rdx and rsi (or, in this case, their lower 32 bits).
Bigger 64-bit values
Just to show that it is in fact ultimately rdi and not edi that is used, I will alter the original one-parameter code to utilise a bigger number:
If you disassemble main, you can see it disassembles to
Last updated
Was this helpful?